From 01c5df0161fc72ae98e09d35bc04338b5bf2a6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lobo?= Date: Mon, 10 Feb 2025 11:05:38 +0000 Subject: [PATCH 1/3] feat: block routes depending on attendee eligibility --- lib/safira/accounts/attendee.ex | 3 +- lib/safira/contest.ex | 1 + .../components/layouts/app.html.heex | 2 +- lib/safira_web/config.ex | 27 +++++++---- .../controllers/download_controller.ex | 2 +- .../live/app/coin_flip_live/index.ex | 45 +++++++++++-------- lib/safira_web/live/app/wheel_live/index.ex | 31 ++++++++----- .../20240714161311_create_attendees.exs | 1 + 8 files changed, 69 insertions(+), 43 deletions(-) diff --git a/lib/safira/accounts/attendee.ex b/lib/safira/accounts/attendee.ex index e10c6da1b..71ddbac43 100644 --- a/lib/safira/accounts/attendee.ex +++ b/lib/safira/accounts/attendee.ex @@ -5,12 +5,13 @@ defmodule Safira.Accounts.Attendee do use Safira.Schema @required_fields ~w(user_id)a - @optional_fields ~w(tokens entries course_id)a + @optional_fields ~w(tokens entries course_id ineligible)a schema "attendees" do field :tokens, :integer, default: 0 field :entries, :integer, default: 0 field :cv, Uploaders.CV.Type + field :ineligible, :boolean, default: false belongs_to :course, Safira.Accounts.Course belongs_to :user, Safira.Accounts.User diff --git a/lib/safira/contest.ex b/lib/safira/contest.ex index 542e73b13..b3a327cf2 100644 --- a/lib/safira/contest.ex +++ b/lib/safira/contest.ex @@ -704,6 +704,7 @@ defmodule Safira.Contest do query |> join(:inner, [dt, rd], at in Safira.Accounts.Attendee, on: at.id == rd.attendee_id) |> join(:inner, [dt, rd, at], u in Safira.Accounts.User, on: u.id == at.user_id) + |> where([dt, rd, at, u], not at.ineligible) |> select([dt, rd, at, u], %{ attendee_id: at.id, position: diff --git a/lib/safira_web/components/layouts/app.html.heex b/lib/safira_web/components/layouts/app.html.heex index 22efa4a4a..bf372aecd 100644 --- a/lib/safira_web/components/layouts/app.html.heex +++ b/lib/safira_web/components/layouts/app.html.heex @@ -3,7 +3,7 @@ <.sidebar :if={Map.get(assigns, :event_started, true)} current_user={@current_user} - pages={SafiraWeb.Config.app_pages()} + pages={SafiraWeb.Config.app_pages(!@current_user.attendee.ineligible)} current_page={Map.get(assigns, :current_page, nil)} background="bg-primary" border="border-darkShade" diff --git a/lib/safira_web/config.ex b/lib/safira_web/config.ex index 27012d1d2..0a8151c73 100644 --- a/lib/safira_web/config.ex +++ b/lib/safira_web/config.ex @@ -43,58 +43,67 @@ defmodule SafiraWeb.Config do |> Enum.filter(fn x -> Enum.member?(enabled_flags, x.feature_flag) end) end - def app_pages do + def app_pages(attendee_eligible?) do if Event.event_started?() do [ %{ key: :badges, title: "Badgedex", icon: "hero-check-badge", - url: "/app/badges" + url: "/app/badges", + enabled: true }, %{ key: :wheel, title: "Wheel", icon: "hero-circle-stack", - url: "/app/wheel" + url: "/app/wheel", + enabled: attendee_eligible? }, %{ key: :coin_flip, title: "Coin Flip", icon: "hero-circle-stack", - url: "/app/coin_flip" + url: "/app/coin_flip", + enabled: attendee_eligible? }, %{ key: :slots, title: "Slots", icon: "hero-circle-stack", - url: "/app/slots" + url: "/app/slots", + enabled: true }, %{ key: :leaderboard, title: "Leaderboard", icon: "hero-trophy", - url: "/app/leaderboard" + url: "/app/leaderboard", + enabled: true }, %{ key: :store, title: "Store", icon: "hero-shopping-bag", - url: "/app/store" + url: "/app/store", + enabled: true }, %{ key: :vault, title: "Vault", icon: "hero-archive-box", - url: "/app/vault" + url: "/app/vault", + enabled: true }, %{ key: :credential, title: "Credential", icon: "hero-qr-code", - url: "/app/credential" + url: "/app/credential", + enabled: true } ] + |> Enum.filter(& &1.enabled) else [] end diff --git a/lib/safira_web/controllers/download_controller.ex b/lib/safira_web/controllers/download_controller.ex index d7e9a0814..c1bc43af2 100644 --- a/lib/safira_web/controllers/download_controller.ex +++ b/lib/safira_web/controllers/download_controller.ex @@ -138,7 +138,7 @@ defmodule SafiraWeb.DownloadController do end defp final_draw_lines(user) do - if user.attendee.entries < 10 do + if user.attendee.entries < 10 and !user.attendee.ineligible do [] else for _ <- 1..user.attendee.entries do diff --git a/lib/safira_web/live/app/coin_flip_live/index.ex b/lib/safira_web/live/app/coin_flip_live/index.ex index 584751804..c2bcb2685 100644 --- a/lib/safira_web/live/app/coin_flip_live/index.ex +++ b/lib/safira_web/live/app/coin_flip_live/index.ex @@ -7,28 +7,35 @@ defmodule SafiraWeb.App.CoinFlipLive.Index do @impl true def mount(_params, _session, socket) do - if connected?(socket) do - Minigames.subscribe_to_coin_flip_config_update("fee") - Minigames.subscribe_to_coin_flip_config_update("is_active") - Minigames.subscribe_to_coin_flip_rooms_update() - end + if socket.assigns.current_user.attendee.ineligible do + {:ok, + socket + |> put_flash(:error, "Can't play the coin flip minigame with this account.") + |> push_navigate(to: ~p"/app")} + else + if connected?(socket) do + Minigames.subscribe_to_coin_flip_config_update("fee") + Minigames.subscribe_to_coin_flip_config_update("is_active") + Minigames.subscribe_to_coin_flip_rooms_update() + end - room_list = Minigames.list_current_coin_flip_rooms() + room_list = Minigames.list_current_coin_flip_rooms() - previous_room_list = Minigames.list_previous_coin_flip_rooms(4) + previous_room_list = Minigames.list_previous_coin_flip_rooms(4) - {:ok, - socket - |> assign(:current_page, :coin_flip) - |> assign(:attendee_tokens, socket.assigns.current_user.attendee.tokens) - |> assign(:coin_flip_fee, Minigames.get_coin_flip_fee()) - |> assign(:result, nil) - |> assign(:coin_flip_active?, Minigames.coin_flip_active?()) - |> assign(:bet, 10) - |> stream(:room_list, room_list) - |> assign(:room_list_count, room_list |> Enum.count()) - |> stream(:previous_room_list, previous_room_list) - |> assign(:previous_room_list_count, previous_room_list |> Enum.count())} + {:ok, + socket + |> assign(:current_page, :coin_flip) + |> assign(:attendee_tokens, socket.assigns.current_user.attendee.tokens) + |> assign(:coin_flip_fee, Minigames.get_coin_flip_fee()) + |> assign(:result, nil) + |> assign(:coin_flip_active?, Minigames.coin_flip_active?()) + |> assign(:bet, 10) + |> stream(:room_list, room_list) + |> assign(:room_list_count, room_list |> Enum.count()) + |> stream(:previous_room_list, previous_room_list) + |> assign(:previous_room_list_count, previous_room_list |> Enum.count())} + end end def handle_event("create-room", _params, socket) do diff --git a/lib/safira_web/live/app/wheel_live/index.ex b/lib/safira_web/live/app/wheel_live/index.ex index 758efe787..52bc8d397 100644 --- a/lib/safira_web/live/app/wheel_live/index.ex +++ b/lib/safira_web/live/app/wheel_live/index.ex @@ -8,19 +8,26 @@ defmodule SafiraWeb.App.WheelLive.Index do @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") - end + if socket.assigns.current_user.attendee.ineligible do + {:ok, + socket + |> put_flash(:error, "Can't play the wheel minigame with this account.") + |> push_navigate(to: ~p"/app")} + else + if connected?(socket) do + Minigames.subscribe_to_wheel_config_update("price") + Minigames.subscribe_to_wheel_config_update("is_active") + end - {:ok, - socket - |> assign(:current_page, :wheel) - |> assign(:in_spin?, false) - |> assign(:attendee_tokens, socket.assigns.current_user.attendee.tokens) - |> assign(:wheel_price, Minigames.get_wheel_price()) - |> assign(:result, nil) - |> assign(:wheel_active?, Minigames.wheel_active?())} + {:ok, + socket + |> assign(:current_page, :wheel) + |> assign(:in_spin?, false) + |> assign(:attendee_tokens, socket.assigns.current_user.attendee.tokens) + |> assign(:wheel_price, Minigames.get_wheel_price()) + |> assign(:result, nil) + |> assign(:wheel_active?, Minigames.wheel_active?())} + end end @impl true diff --git a/priv/repo/migrations/20240714161311_create_attendees.exs b/priv/repo/migrations/20240714161311_create_attendees.exs index a9a9d7732..980e7357f 100644 --- a/priv/repo/migrations/20240714161311_create_attendees.exs +++ b/priv/repo/migrations/20240714161311_create_attendees.exs @@ -7,6 +7,7 @@ defmodule Safira.Repo.Migrations.CreateAttendees do add :tokens, :integer, default: 0 add :entries, :integer, default: 0 add :cv, :string, null: true + add :ineligible, :boolean, default: false add :course_id, references(:courses, type: :binary_id, on_delete: :delete_all) add :user_id, references(:users, type: :binary_id, on_delete: :delete_all), null: false From 9d1b0bdc6354cd3f50204d0cb714997741eaf7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lobo?= Date: Mon, 10 Feb 2025 12:51:29 +0000 Subject: [PATCH 2/3] feat: add warning --- .../backoffice/attendee_live/show.html.heex | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/safira_web/live/backoffice/attendee_live/show.html.heex b/lib/safira_web/live/backoffice/attendee_live/show.html.heex index 942989cba..4f6323840 100644 --- a/lib/safira_web/live/backoffice/attendee_live/show.html.heex +++ b/lib/safira_web/live/backoffice/attendee_live/show.html.heex @@ -28,13 +28,30 @@
<.icon name="hero-circle-stack" class="w-5 h-5" /> -

<%= "#{@attendee.tokens} tokens" %>

+
+

<%= "#{@attendee.tokens} tokens" %>

+ * +
- <.icon name="hero-qr-code" class="w-5 h-5" /> -

<%= "#{@attendee.entries} entries" %>

+ <.icon name="hero-ticket" class="w-5 h-5" /> +
+

<%= "#{@attendee.entries} entries" %>

+ * +
+
+ <.icon name="hero-information-circle" class="w-6 h-6 flex-shrink-0" /> +

+ <%= gettext( + "*This attendee is not eligible to win daily prizes, play minigames that involve other attendees, or participate in the final draw." + ) %> +

+
From 504609ba1d03e3c021a53ede7189bcdb59768a7d Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Mon, 10 Feb 2025 16:52:10 +0000 Subject: [PATCH 3/3] feat: backoffice edit button --- lib/safira_web.ex | 2 +- .../ineligible_live/form_component.ex | 85 +++++++++++++++++++ .../backoffice/attendee_live/show.html.heex | 43 +++++++--- lib/safira_web/router.ex | 1 + priv/static/30anos.html | 24 ++++++ 5 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 lib/safira_web/live/backoffice/attendee_live/ineligible_live/form_component.ex create mode 100644 priv/static/30anos.html diff --git a/lib/safira_web.ex b/lib/safira_web.ex index d3cd86d65..67ec58f49 100644 --- a/lib/safira_web.ex +++ b/lib/safira_web.ex @@ -17,7 +17,7 @@ defmodule SafiraWeb do those modules here. """ - def static_paths, do: ~w(assets docs fonts images models favicon.ico robots.txt) + def static_paths, do: ~w(assets docs fonts images models favicon.ico robots.txt 30anos.html) def router do quote do diff --git a/lib/safira_web/live/backoffice/attendee_live/ineligible_live/form_component.ex b/lib/safira_web/live/backoffice/attendee_live/ineligible_live/form_component.ex new file mode 100644 index 000000000..00ea85171 --- /dev/null +++ b/lib/safira_web/live/backoffice/attendee_live/ineligible_live/form_component.ex @@ -0,0 +1,85 @@ +defmodule SafiraWeb.Backoffice.AttendeeLive.IneligibleLive.FormComponent do + use SafiraWeb, :live_component + + alias Safira.Accounts + import SafiraWeb.Components.Forms + + @impl true + def render(assigns) do + ~H""" +
+ <.page + title="Eligibility" + subtitle={gettext("Eligibility settings for %{name}.", name: assigns.attendee.user.name)} + > + <.simple_form + for={@form} + id="ineligible-form" + phx-target={@myself} + phx-change="validate" + phx-submit="save" + > + <.field field={@form[:ineligible]} label="Ineligible" type="switch" /> + <:actions> + <.button phx-disable-with="Saving..."> + <%= gettext("Save Eligibility") %> + + + + +
+ """ + end + + @impl true + def mount(socket) do + {:ok, socket |> assign(:live_action, :default)} + end + + @impl true + def update(%{attendee: attendee} = assigns, socket) do + form = Accounts.change_attendee(attendee, %{}) + + {:ok, + socket + |> assign(assigns) + |> assign_new(:form, fn -> + to_form(form) + end)} + end + + @impl true + def handle_event( + "validate", + %{"attendee" => attendee_params}, + socket + ) do + changeset = Accounts.change_attendee(socket.assigns.attendee, attendee_params) + + {:noreply, + socket + |> assign(:form, to_form(changeset, action: :validate))} + end + + @impl true + def handle_event("save", %{"attendee" => attendee_params}, socket) do + attendee = socket.assigns.attendee + + case Accounts.update_attendee(attendee, attendee_params) do + {:ok, _attendee} -> + {:noreply, socket |> push_navigate(to: socket.assigns.patch)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign(socket, form: to_form(changeset, action: :validate))} + end + end + + @impl true + def handle_event("confirm-modal", _params, socket) do + {:noreply, assign(socket, live_action: :confirm_eligibility)} + end + + def handle_event("cancel", _params, socket) do + {:noreply, assign(socket, live_action: :default)} + end +end diff --git a/lib/safira_web/live/backoffice/attendee_live/show.html.heex b/lib/safira_web/live/backoffice/attendee_live/show.html.heex index 4f6323840..6602dbabb 100644 --- a/lib/safira_web/live/backoffice/attendee_live/show.html.heex +++ b/lib/safira_web/live/backoffice/attendee_live/show.html.heex @@ -7,6 +7,11 @@ <.icon name="hero-currency-euro" class="w-5 h-5" /> + <.link patch={~p"/dashboard/attendees/#{@attendee.id}/edit/eligibility"}> + <.button> + <.icon name="hero-check-badge" class="w-5 h-5" /> + + <.button> <.icon name="hero-flag" class="w-5 h-5" /> @@ -41,17 +46,17 @@ -
- <.icon name="hero-information-circle" class="w-6 h-6 flex-shrink-0" /> -

- <%= gettext( - "*This attendee is not eligible to win daily prizes, play minigames that involve other attendees, or participate in the final draw." - ) %> -

-
+ +
+ <.icon name="hero-information-circle" class="w-6 h-6 flex-shrink-0" /> +

+ <%= gettext( + "*This attendee is not eligible to win daily prizes, play minigames that involve other attendees, or participate in the final draw." + ) %> +

@@ -70,3 +75,19 @@ patch={~p"/dashboard/attendees/#{@attendee.id}"} /> + +<.modal + :if={@live_action in [:eligibility_edit]} + id="attendee-tokens-modal" + show + on_cancel={JS.patch(~p"/dashboard/attendees/#{@attendee.id}")} +> + <.live_component + module={SafiraWeb.Backoffice.AttendeeLive.IneligibleLive.FormComponent} + id={@attendee.id} + current_user={@current_user} + action={@live_action} + attendee={@attendee} + patch={~p"/dashboard/attendees/#{@attendee.id}"} + /> + diff --git a/lib/safira_web/router.ex b/lib/safira_web/router.ex index 981467683..8cdbb68a5 100644 --- a/lib/safira_web/router.ex +++ b/lib/safira_web/router.ex @@ -170,6 +170,7 @@ defmodule SafiraWeb.Router do live "/", Index, :index live "/:id", Show, :show live "/:id/edit/tokens", Show, :tokens_edit + live "/:id/edit/eligibility", Show, :eligibility_edit end scope "/event", EventLive do diff --git a/priv/static/30anos.html b/priv/static/30anos.html new file mode 100644 index 000000000..5a63fd461 --- /dev/null +++ b/priv/static/30anos.html @@ -0,0 +1,24 @@ + + + + + + + + 30 anos CeSIUM + + + + + + + + + +

30 anos!!

+ +