Skip to content

Commit 70a2b0f

Browse files
committed
feat: add exclusive bounty sharing functionality
- Introduced a new `ExclusiveBountyForm` for sharing bounties with specific users. - Implemented changeset validation for the exclusive bounty form. - Updated `BountyLive` to handle exclusive sharing events and manage the corresponding modal. - Enhanced the UI to include a drawer for exclusive sharing with GitHub handle and deadline inputs.
1 parent 9f28d01 commit 70a2b0f

File tree

2 files changed

+123
-17
lines changed

2 files changed

+123
-17
lines changed

lib/algora/bounties/schemas/bounty.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ defmodule Algora.Bounties.Bounty do
4242
|> Algora.Validations.validate_money_positive(:amount)
4343
end
4444

45+
def settings_changeset(bounty, attrs) do
46+
bounty
47+
|> cast(attrs, [:visibility, :shared_with])
48+
|> validate_required([:visibility, :shared_with])
49+
end
50+
4551
def url(%{repository: %{name: name, owner: %{login: login}}, ticket: %{provider: "github", number: number}}) do
4652
"https://github.com/#{login}/#{name}/issues/#{number}"
4753
end

lib/algora_web/live/bounty_live.ex

Lines changed: 117 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ defmodule AlgoraWeb.BountyLive do
55
import Ecto.Changeset
66

77
alias Algora.Accounts
8-
alias Algora.Accounts.User
98
alias Algora.Bounties
109
alias Algora.Bounties.Bounty
1110
alias Algora.Bounties.LineItem
@@ -43,6 +42,25 @@ defmodule AlgoraWeb.BountyLive do
4342
end
4443
end
4544

45+
defmodule ExclusiveBountyForm do
46+
@moduledoc false
47+
use Ecto.Schema
48+
49+
import Ecto.Changeset
50+
51+
@primary_key false
52+
embedded_schema do
53+
field :github_handle, :string
54+
field :deadline, :date
55+
end
56+
57+
def changeset(form, attrs) do
58+
form
59+
|> cast(attrs, [:github_handle, :deadline])
60+
|> validate_required([:github_handle, :deadline])
61+
end
62+
end
63+
4664
@impl true
4765
def mount(%{"id" => bounty_id}, _session, socket) do
4866
bounty =
@@ -65,12 +83,18 @@ defmodule AlgoraWeb.BountyLive do
6583

6684
contexts = contexts(bounty)
6785

68-
changeset =
86+
reward_changeset =
6987
RewardBountyForm.changeset(%RewardBountyForm{}, %{
7088
tip_percentage: 0,
7189
amount: Money.to_decimal(bounty.amount)
7290
})
7391

92+
exclusive_changeset =
93+
ExclusiveBountyForm.changeset(%ExclusiveBountyForm{}, %{
94+
github_handle: "",
95+
deadline: Date.utc_today()
96+
})
97+
7498
{:ok,
7599
socket
76100
|> assign(:page_title, bounty.ticket.title)
@@ -80,9 +104,11 @@ defmodule AlgoraWeb.BountyLive do
80104
|> assign(:ticket_body_html, ticket_body_html)
81105
|> assign(:contexts, contexts)
82106
|> assign(:show_reward_modal, false)
107+
|> assign(:show_exclusive_modal, false)
83108
|> assign(:selected_context, nil)
84109
|> assign(:line_items, [])
85-
|> assign(:reward_form, to_form(changeset))}
110+
|> assign(:reward_form, to_form(reward_changeset))
111+
|> assign(:exclusive_form, to_form(exclusive_changeset))}
86112
end
87113

88114
@impl true
@@ -103,8 +129,12 @@ defmodule AlgoraWeb.BountyLive do
103129
{:noreply, assign(socket, :show_reward_modal, true)}
104130
end
105131

132+
def handle_event("exclusive", _params, socket) do
133+
{:noreply, assign(socket, :show_exclusive_modal, true)}
134+
end
135+
106136
def handle_event("close_drawer", _params, socket) do
107-
{:noreply, assign(socket, :show_reward_modal, false)}
137+
{:noreply, close_drawers(socket)}
108138
end
109139

110140
def handle_event("validate_reward", %{"reward_bounty_form" => params}, socket) do
@@ -133,6 +163,35 @@ defmodule AlgoraWeb.BountyLive do
133163
end
134164
end
135165

166+
def handle_event("validate_exclusive", %{"exclusive_bounty_form" => params}, socket) do
167+
{:noreply,
168+
socket
169+
|> assign(:exclusive_form, to_form(ExclusiveBountyForm.changeset(%ExclusiveBountyForm{}, params)))
170+
|> assign_line_items()}
171+
end
172+
173+
def handle_event("share_exclusive", %{"exclusive_bounty_form" => params}, socket) do
174+
changeset = ExclusiveBountyForm.changeset(%ExclusiveBountyForm{}, params)
175+
bounty = socket.assigns.bounty
176+
177+
case apply_action(changeset, :save) do
178+
{:ok, data} ->
179+
case bounty
180+
|> Bounty.settings_changeset(%{shared_with: Enum.uniq(bounty.shared_with ++ [data.github_handle])})
181+
|> Repo.update() do
182+
{:ok, _} ->
183+
{:noreply, socket |> put_flash(:info, "Bounty shared") |> close_drawers()}
184+
185+
{:error, _reason} ->
186+
Logger.error("Failed to share bounty: #{inspect(_reason)}")
187+
{:noreply, put_flash(socket, :error, "Something went wrong")}
188+
end
189+
190+
{:error, changeset} ->
191+
{:noreply, assign(socket, :exclusive_form, to_form(changeset))}
192+
end
193+
end
194+
136195
defp assign_selected_context(socket, context_id) do
137196
case Enum.find(socket.assigns.contexts, &(&1.id == context_id)) do
138197
nil ->
@@ -144,13 +203,17 @@ defmodule AlgoraWeb.BountyLive do
144203
end
145204

146205
defp assign_line_items(socket) do
147-
line_items =
148-
Bounties.generate_line_items(
149-
%{amount: calculate_final_amount(socket.assigns.reward_form.source)},
150-
ticket_ref: ticket_ref(socket),
151-
recipient: socket.assigns.selected_context
152-
)
153-
206+
# line_items =
207+
# Bounties.generate_line_items(
208+
# %{
209+
# owner: socket.assigns.selected_context,
210+
# amount: calculate_final_amount(socket.assigns.reward_form.source)
211+
# },
212+
# ticket_ref: ticket_ref(socket),
213+
# recipient: socket.assigns.selected_context
214+
# )
215+
216+
line_items = []
154217
assign(socket, :line_items, line_items)
155218
end
156219

@@ -264,14 +327,14 @@ defmodule AlgoraWeb.BountyLive do
264327
<.card_title>
265328
Shared with
266329
</.card_title>
267-
<.button phx-click="invite">
268-
Invite
330+
<.button phx-click="exclusive">
331+
Share Exclusive
269332
</.button>
270333
</div>
271334
</.card_header>
272335
<.card_content>
273336
<div class="space-y-4">
274-
<div class="flex justify-between text-sm">
337+
<%!-- <div class="flex justify-between text-sm">
275338
<span>
276339
<div class="flex items-center gap-4">
277340
<.avatar>
@@ -307,12 +370,12 @@ defmodule AlgoraWeb.BountyLive do
307370
</div>
308371
</span>
309372
</div>
310-
<% end %>
311-
<%= for user <- invited_users(@bounty) do %>
373+
<% end %> --%>
374+
<%= for user <- @bounty.shared_with do %>
312375
<div class="flex justify-between text-sm">
313376
<span>
314377
<div class="flex items-center gap-4">
315-
<.icon name="tabler-mail" class="h-10 w-10 text-muted-foreground" />
378+
<.icon name="github" class="h-10 w-10 text-muted-foreground" />
316379
<div>
317380
<p class="font-medium">{user}</p>
318381
</div>
@@ -327,6 +390,37 @@ defmodule AlgoraWeb.BountyLive do
327390
</div>
328391
</div>
329392
393+
<.drawer
394+
:if={@current_user}
395+
show={@show_exclusive_modal}
396+
on_cancel="close_drawer"
397+
direction="right"
398+
>
399+
<.drawer_header>
400+
<.drawer_title>Share Exclusive</.drawer_title>
401+
<.drawer_description>
402+
Make this bounty exclusive to specific users
403+
</.drawer_description>
404+
</.drawer_header>
405+
<.drawer_content class="mt-4">
406+
<.form for={@exclusive_form} phx-change="validate_exclusive" phx-submit="share_exclusive">
407+
<div class="flex flex-col gap-8">
408+
<div class="space-y-4">
409+
<.input label="GitHub handle" field={@exclusive_form[:github_handle]} />
410+
<.input type="date" label="Deadline" field={@exclusive_form[:deadline]} />
411+
</div>
412+
<div class="ml-auto flex gap-4">
413+
<.button variant="secondary" phx-click="close_drawer" type="button">
414+
Cancel
415+
</.button>
416+
<.button type="submit">
417+
Submit
418+
</.button>
419+
</div>
420+
</div>
421+
</.form>
422+
</.drawer_content>
423+
</.drawer>
330424
<.drawer :if={@current_user} show={@show_reward_modal} on_cancel="close_drawer">
331425
<.drawer_header>
332426
<.drawer_title>Reward Bounty</.drawer_title>
@@ -464,4 +558,10 @@ defmodule AlgoraWeb.BountyLive do
464558
defp bounty_frequency(_bounty) do
465559
"Monthly"
466560
end
561+
562+
defp close_drawers(socket) do
563+
socket
564+
|> assign(:show_reward_modal, false)
565+
|> assign(:show_exclusive_modal, false)
566+
end
467567
end

0 commit comments

Comments
 (0)