Skip to content

Commit 263d67b

Browse files
committed
feat: add payable bounties section to org dashboard
1 parent a953523 commit 263d67b

File tree

3 files changed

+148
-1
lines changed

3 files changed

+148
-1
lines changed

.iex.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ alias Algora.Accounts
66
alias Algora.Accounts.Identity
77
alias Algora.Accounts.User
88
alias Algora.Admin
9+
alias Algora.Bounties
10+
alias Algora.Bounties.Claim
911
alias Algora.Contracts
1012
alias Algora.Contracts.Contract
1113
alias Algora.Contracts.Timesheet

lib/algora_web/live/org/dashboard_live.ex

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,22 @@ defmodule AlgoraWeb.Org.DashboardLive do
44

55
import AlgoraWeb.Components.Achievement
66
import Ecto.Changeset
7+
import Ecto.Query
78

89
alias Algora.Accounts
910
alias Algora.Accounts.User
1011
alias Algora.Bounties
12+
alias Algora.Bounties.Bounty
13+
alias Algora.Bounties.Claim
1114
alias Algora.Contracts
1215
alias Algora.Github
16+
alias Algora.Payments.Transaction
17+
alias Algora.Repo
1318
alias Algora.Types.USD
1419
alias Algora.Validations
1520
alias Algora.Workspace
21+
alias Algora.Workspace.Repository
22+
alias Algora.Workspace.Ticket
1623
alias AlgoraWeb.Components.Logos
1724

1825
require Logger
@@ -107,6 +114,7 @@ defmodule AlgoraWeb.Org.DashboardLive do
107114
|> assign(:contract_form, to_form(ContractForm.changeset(%ContractForm{}, %{})))
108115
|> assign(:show_contract_modal, false)
109116
|> assign(:selected_developer, nil)
117+
|> assign_payable_bounties()
110118
|> assign_contracts()
111119
|> assign_achievements()}
112120
else
@@ -119,6 +127,115 @@ defmodule AlgoraWeb.Org.DashboardLive do
119127
~H"""
120128
<div class="lg:pr-96">
121129
<div class="container mx-auto max-w-7xl space-y-8 p-8">
130+
<.section :if={@payable_bounties != []}>
131+
<.card>
132+
<.card_header>
133+
<.card_title>Pending Payments</.card_title>
134+
<.card_description>
135+
The following claims have been approved and are ready for payment.
136+
</.card_description>
137+
</.card_header>
138+
<.card_content>
139+
<table class="w-full caption-bottom text-sm">
140+
<tbody class="[&_tr:last-child]:border-0">
141+
<%= for {_group_id, [%{target: %{bounties: [bounty | _]}} | _] = claims} <- @payable_bounties do %>
142+
<tr
143+
class="bg-white/[2%] from-white/[2%] via-white/[2%] to-white/[2%] border-b border-white/15 bg-gradient-to-br transition-colors data-[state=selected]:bg-gray-100 hover:bg-gray-100/50 dark:data-[state=selected]:bg-gray-800 dark:hover:bg-white/[2%]"
144+
data-state="false"
145+
>
146+
<td colspan={2} class="[&:has([role=checkbox])]:pr-0 p-4 align-middle">
147+
<div class="min-w-[250px]">
148+
<div class="group relative flex h-full flex-col">
149+
<div class="relative h-full pl-2">
150+
<div class="flex items-start justify-between">
151+
<div class="cursor-pointer font-mono text-2xl">
152+
<div class="font-extrabold text-emerald-300 hover:text-emerald-200">
153+
{Money.to_string!(bounty.amount)}
154+
</div>
155+
</div>
156+
</div>
157+
<.link
158+
rel="noopener"
159+
class="group/issue inline-flex flex-col"
160+
href={Bounty.url(bounty)}
161+
>
162+
<div class="flex items-center gap-4">
163+
<div class="truncate">
164+
<p class="truncate text-sm font-medium text-gray-300 group-hover/issue:text-gray-200 group-hover/issue:underline">
165+
{Bounty.path(bounty)}
166+
</p>
167+
</div>
168+
</div>
169+
<p class="line-clamp-2 break-words text-base font-medium leading-tight text-gray-100 group-hover/issue:text-white group-hover/issue:underline">
170+
{bounty.ticket.title}
171+
</p>
172+
</.link>
173+
<p class="flex items-center gap-1.5 text-xs text-gray-400">
174+
{Algora.Util.time_ago(bounty.inserted_at)}
175+
</p>
176+
</div>
177+
</div>
178+
</div>
179+
</td>
180+
</tr>
181+
<tr
182+
class="border-b border-white/15 bg-gray-950/50 transition-colors data-[state=selected]:bg-gray-100 hover:bg-gray-100/50 dark:data-[state=selected]:bg-gray-800 dark:hover:bg-gray-950/50"
183+
data-state="false"
184+
>
185+
<td class="[&:has([role=checkbox])]:pr-0 p-4 align-middle w-full">
186+
<div class="min-w-[250px]">
187+
<div class="flex items-center gap-3">
188+
<div class="flex -space-x-3">
189+
<%= for claim <- claims do %>
190+
<div class="relative h-10 w-10 flex-shrink-0 rounded-full ring-4 ring-background">
191+
<img
192+
alt={User.handle(claim.user)}
193+
loading="lazy"
194+
decoding="async"
195+
class="rounded-full"
196+
src={claim.user.avatar_url}
197+
style="position: absolute; height: 100%; width: 100%; inset: 0px; color: transparent;"
198+
/>
199+
</div>
200+
<% end %>
201+
</div>
202+
<div>
203+
<div class="text-sm font-medium text-gray-200">
204+
{claims
205+
|> Enum.map(fn c -> User.handle(c.user) end)
206+
|> Algora.Util.format_name_list()}
207+
</div>
208+
<div class="text-xs text-gray-400">
209+
{Algora.Util.time_ago(hd(claims).inserted_at)}
210+
</div>
211+
</div>
212+
</div>
213+
</div>
214+
</td>
215+
<td class="[&:has([role=checkbox])]:pr-0 p-4 align-middle">
216+
<div class="min-w-[180px]">
217+
<div class="flex items-center justify-end gap-4">
218+
<.button
219+
:if={hd(claims).source}
220+
href={hd(claims).source.url}
221+
variant="secondary"
222+
>
223+
View
224+
</.button>
225+
<.button href={~p"/claims/#{hd(claims).group_id}"}>
226+
Reward
227+
</.button>
228+
</div>
229+
</div>
230+
</td>
231+
</tr>
232+
<% end %>
233+
</tbody>
234+
</table>
235+
</.card_content>
236+
</.card>
237+
</.section>
238+
122239
<.section :if={@installations == []}>
123240
<.card>
124241
<.card_header>
@@ -635,6 +752,34 @@ defmodule AlgoraWeb.Org.DashboardLive do
635752
end
636753
end
637754

755+
defp assign_payable_bounties(socket) do
756+
org = socket.assigns.current_org
757+
758+
payable_claims =
759+
Repo.all(
760+
from c in Claim,
761+
where: c.status == :approved,
762+
join: t in assoc(c, :target),
763+
join: b in assoc(t, :bounties),
764+
where: b.owner_id == ^org.id,
765+
left_join: tx in Transaction,
766+
on: tx.claim_id == c.id and tx.type == :debit,
767+
where: is_nil(tx.status) or tx.status not in [:initialized, :succeeded],
768+
join: r in assoc(t, :repository),
769+
join: ru in assoc(r, :user),
770+
join: cu in assoc(c, :user),
771+
left_join: s in assoc(c, :source),
772+
select_merge: %{
773+
user: cu,
774+
source: s,
775+
target: %Ticket{t | bounties: [%Bounty{b | ticket: %{t | repository: %{r | user: ru}}}]}
776+
}
777+
)
778+
779+
payable_bounties = Enum.group_by(payable_claims, & &1.group_id)
780+
assign(socket, :payable_bounties, payable_bounties)
781+
end
782+
638783
defp assign_contracts(socket) do
639784
contracts = Contracts.list_contracts(client_id: socket.assigns.current_org.id, status: :draft)
640785

priv/repo/seeds.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ for {repo_name, issues} <- repos do
560560
target_id: issue.id,
561561
source_id: pull_request.id,
562562
type: :pull_request,
563-
status: if(paid, do: :approved, else: :pending),
563+
status: :approved,
564564
url: "https://github.com/piedpiper/#{repo_name}/pull/#{index}"
565565
})
566566

0 commit comments

Comments
 (0)