Skip to content

Commit

Permalink
feat: badge revokes (#526)
Browse files Browse the repository at this point in the history
Co-authored-by: Nuno Miguel <[email protected]>
  • Loading branch information
AfonsoMartins26 and nunom27 authored Feb 13, 2025
1 parent 90ab1bf commit 2d0ae3c
Show file tree
Hide file tree
Showing 12 changed files with 322 additions and 7 deletions.
37 changes: 36 additions & 1 deletion lib/safira/contest.ex
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ defmodule Safira.Contest do
|> Repo.all()
end

def list_attendee_redeems_meta(attendee_id, params) do
BadgeRedeem
|> join(:inner, [br], b in Badge, on: b.id == br.badge_id, as: :badge)
|> where([br, b], br.attendee_id == ^attendee_id)
|> preload([:badge, attendee: [:user], redeemed_by: [:user]])
|> order_by([br], desc: br.inserted_at)
|> Flop.validate_and_run(params, for: BadgeRedeem)
end

@doc """
Lists all badge redeems belonging to a badge.
Expand Down Expand Up @@ -168,7 +177,7 @@ defmodule Safira.Contest do
|> where([br], br.badge_id == ^badge_id)
|> join(:inner, [br], a in assoc(br, :attendee), as: :attendee)
|> join(:inner, [br, a], u in assoc(a, :user), as: :user)
|> preload(attendee: [:user])
|> preload(attendee: [:user], redeemed_by: [:user])
|> apply_filters(opts)
|> Flop.validate_and_run(params, for: BadgeRedeem)
end
Expand Down Expand Up @@ -376,6 +385,32 @@ defmodule Safira.Contest do
Repo.get!(DailyPrize, id)
end

def revoke_badge_redeem_from_attendee(badge_redeem_id) do
revoke_badge_redeem_transaction(badge_redeem_id)
end

defp revoke_badge_redeem_transaction(badge_redeem_id) do
Multi.new()
|> Multi.run(:badge_redeem, fn _repo, _changes ->
{:ok, get_badge_redeem!(badge_redeem_id, preloads: [:badge, :attendee])}
end)
|> Multi.delete(:remove_badge_from_attendee, fn %{badge_redeem: badge_redeem} ->
badge_redeem
end)
|> Multi.update(:attendee_update_entries, fn %{badge_redeem: badge_redeem} ->
Attendee.changeset(badge_redeem.attendee, %{
entries: max(badge_redeem.attendee.entries - badge_redeem.badge.entries, 0)
})
end)
|> Multi.merge(fn %{badge_redeem: badge_redeem} ->
change_attendee_tokens_transaction(
badge_redeem.attendee,
max(badge_redeem.attendee.tokens - badge_redeem.badge.tokens, 0)
)
end)
|> Repo.transaction()
end

@doc """
Creates a daily prize.
Expand Down
6 changes: 5 additions & 1 deletion lib/safira/contest/badge_redeem.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ defmodule Safira.Contest.BadgeRedeem do
default_limit: 10,
max_limit: 50,
join_fields: [
name: [binding: :user, field: :name]
name: [
binding: :badge,
field: :name
]
]}

schema "badge_redeems" do
belongs_to :badge, Badge

Expand Down
2 changes: 1 addition & 1 deletion lib/safira_web/components/table.ex
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ defmodule SafiraWeb.Components.Table do
~H"""
<li>
<p class={[
"hover:cursor-default select-none flex items-center justify-center px-3 h-8 ms-0 leading-tight text-gray-500 border border-gray-300 bg-gray-100 border-lightShade dark:border-darkShade dark:text-gray-400 dark:bg-dark",
"hover:cursor-default select-none flex items-center justify-center px-3 h-8 ms-0 leading-tight text-gray-500 border border-lightShade dark:border-darkShade dark:text-gray-400 dark:bg-dark",
@right_corner && "rounded-e-lg",
@left_corner && "rounded-s-lg"
]}>
Expand Down
2 changes: 1 addition & 1 deletion lib/safira_web/live/backoffice/attendee_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule SafiraWeb.Backoffice.AttendeeLive.Index do

@impl true
def handle_params(params, _, socket) do
case Accounts.list_attendees(params) do
case Accounts.list_attendees(params, order_by: [desc: :inserted_at]) do
{:ok, {attendees, meta}} ->
{:noreply,
socket
Expand Down
100 changes: 100 additions & 0 deletions lib/safira_web/live/backoffice/attendee_live/redeem_live/index.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
defmodule SafiraWeb.Backoffice.AttendeeLive.RedeemLive.Index do
use SafiraWeb, :live_component

alias Safira.Contest

import SafiraWeb.Components.{Badge, Table, TableSearch}

on_mount {SafiraWeb.StaffRoles, index: %{"badges" => ["revoke"]}}

@limit 5

@impl true
def render(assigns) do
~H"""
<div>
<.page title={@title} subtitle={gettext("Refund badge redeems.")}>
<:actions>
<div class="flex flex-row w-full gap-4">
<.table_search
id="badges-table-name-search"
params={@params}
field={:name}
path={~p"/dashboard/attendees/#{@attendee.id}/redeem"}
placeholder={gettext("Search for badges")}
/>
</div>
</:actions>
<.table id="speakers-table" items={@streams.redeems} meta={@meta} params={@params}>
<:col :let={{_id, redeem}} field={:badge} label="Badge">
<.badge id={redeem.badge.id} badge={redeem.badge} width="max-w-16" />
<div class="flex gap-4 flex-center max-w-16"></div>
</:col>
<:col :let={{_id, redeem}} field={:redeemed_by} label="Redeemed by">
<%= if redeem.redeemed_by, do: redeem.redeemed_by.user.name, else: "System / Company" %>
</:col>
<:col :let={{_id, redeem}} sortable field={:inserted_at} label="Redeemed at">
<%= datetime_to_string(redeem.inserted_at) %>
</:col>
<:action :let={{id, speaker}}>
<div class="flex flex-row gap-2">
<.link
phx-click={
JS.push("delete", value: %{id: speaker.id}, target: @myself) |> hide("##{id}")
}
data-confirm="Are you sure?"
>
<.icon name="hero-trash" class="w-5 h-5" />
</.link>
</div>
</:action>
</.table>
</.page>
</div>
"""
end

@impl true
def mount(socket) do
{:ok, socket}
end

@impl true
def update(assigns, socket) do
case Contest.list_attendee_redeems_meta(
assigns.attendee.id,
Map.put(assigns.params, "page_size", @limit)
) do
{:ok, {redeems, meta}} ->
{:ok,
socket
|> assign(assigns)
|> assign(meta: meta)
|> stream(
:redeems,
redeems,
reset: true
)}

{:error, _error} ->
{:ok, socket}
end
end

@impl true
def handle_event("delete", %{"id" => id}, socket) do
badge_redeem = Contest.get_badge_redeem!(id)

case Contest.revoke_badge_redeem_from_attendee(id) do
{:ok, _} ->
{:noreply, stream_delete(socket, :redeems, badge_redeem)}

{:error, _reason} ->
{:noreply, socket}
end
end

defp datetime_to_string(datetime) do
Timex.format!(datetime, "%D %T", :strftime)
end
end
5 changes: 3 additions & 2 deletions lib/safira_web/live/backoffice/attendee_live/show.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ defmodule SafiraWeb.Backoffice.AttendeeLive.Show do
{:ok, socket |> assign(:current_page, :attendees)}
end

def handle_params(%{"id" => attendee_id}, _, socket) do
def handle_params(%{"id" => attendee_id} = params, _, socket) do
{:noreply,
socket
|> assign(:attendee, Accounts.get_attendee!(attendee_id, preloads: [:user]))}
|> assign(:attendee, Accounts.get_attendee!(attendee_id, preloads: [:user]))
|> assign(:params, params)}
end
end
23 changes: 23 additions & 0 deletions lib/safira_web/live/backoffice/attendee_live/show.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
</.button>
</.link>
<.link patch={~p"/dashboard/attendees/#{@attendee.id}/edit/eligibility"}>
<.button>
<.icon name="hero-shield-exclamation" class="w-5 h-5" />
</.button>
</.link>
<.link patch={~p"/dashboard/attendees/#{@attendee.id}/redeem"}>
<.button>
<.icon name="hero-check-badge" class="w-5 h-5" />
</.button>
Expand Down Expand Up @@ -99,3 +104,21 @@
patch={~p"/dashboard/attendees/#{@attendee.id}"}
/>
</.modal>

<.modal
:if={@live_action in [:redeem]}
id="attendee-tokens-modal"
show
on_cancel={JS.patch(~p"/dashboard/attendees/#{@attendee.id}")}
>
<.live_component
module={SafiraWeb.Backoffice.AttendeeLive.RedeemLive.Index}
title="Badge redeems"
id={@attendee.id}
current_user={@current_user}
params={@params}
action={@live_action}
attendee={@attendee}
patch={~p"/dashboard/attendees/#{@attendee.id}"}
/>
</.modal>
5 changes: 5 additions & 0 deletions lib/safira_web/live/backoffice/badge_live/form_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ defmodule SafiraWeb.Backoffice.BadgeLive.FormComponent do
<.icon name="hero-check-circle" />
</.button>
</.link>
<.link :if={@badge.id} patch={~p"/dashboard/badges/#{@badge.id}/redeems"}>
<.button>
<.icon name="hero-check-badge" />
</.button>
</.link>
</:actions>
<.simple_form
for={@form}
Expand Down
14 changes: 13 additions & 1 deletion lib/safira_web/live/backoffice/badge_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ defmodule SafiraWeb.Backoffice.BadgeLive.Index do

@impl true
def handle_params(params, _url, socket) do
case Contest.list_badges(params) do
badge_params = if socket.assigns.live_action == :index, do: params, else: %{}

case Contest.list_badges(badge_params) do
{:ok, {badges, meta}} ->
{:noreply,
socket
Expand Down Expand Up @@ -128,6 +130,16 @@ defmodule SafiraWeb.Backoffice.BadgeLive.Index do
|> assign(:page_title, "Import Badges")
end

defp apply_action(socket, :redeem, %{"id" => id} = params) do
badge = Contest.get_badge!(id)

socket
|> assign(:page_title, "Redeem Badge")
|> assign(:redeem_params, params)
|> assign(:params, %{})
|> assign(:badge, badge)
end

@impl true
def handle_event("delete", %{"id" => id}, socket) do
badge = Contest.get_badge!(id)
Expand Down
18 changes: 18 additions & 0 deletions lib/safira_web/live/backoffice/badge_live/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,21 @@
patch={~p"/dashboard/badges"}
/>
</.modal>

<.modal
:if={@live_action in [:redeem]}
id="import-modal"
show
on_cancel={JS.patch(~p"/dashboard/badges")}
>
<.live_component
module={SafiraWeb.Backoffice.BadgeLive.RedeemLive.Index}
id="import"
title={@page_title}
current_user={@current_user}
params={@redeem_params}
action={@live_action}
patch={~p"/dashboard/badges"}
badge={@badge}
/>
</.modal>
Loading

0 comments on commit 2d0ae3c

Please sign in to comment.