@@ -14,14 +14,17 @@ defmodule AlgoraWeb.User.ProfileLive do
1414 { :ok , user } ->
1515 transactions = Payments . list_received_transactions ( user . id , limit: page_size ( ) )
1616
17+ contributions = Algora.Workspace . list_user_contributions ( user . provider_login , limit: 20 )
18+
1719 { :ok ,
1820 socket
1921 |> assign ( :user , user )
2022 |> assign ( :page_title , "#{ user . name } " )
2123 |> assign ( :page_description , "Open source contributions and bounty history of #{ user . name } " )
2224 |> assign ( :reviews , Reviews . list_reviews ( reviewee_id: user . id , limit: 10 ) )
2325 |> assign ( :transactions , to_transaction_rows ( transactions ) )
24- |> assign ( :has_more_transactions , length ( transactions ) >= page_size ( ) ) }
26+ |> assign ( :has_more_transactions , length ( transactions ) >= page_size ( ) )
27+ |> assign ( :contributions , contributions ) }
2528
2629 { :error , :not_found } ->
2730 raise AlgoraWeb.NotFoundError
@@ -188,46 +191,95 @@ defmodule AlgoraWeb.User.ProfileLive do
188191 <!-- Reviews Column -->
189192 < div class = "space-y-4 " >
190193 < div class = "grid grid-cols-1 gap-4 " >
191- < h2 class = "text-lg font-semibold " > Completed Contracts</ h2 >
192- <%= if Enum . empty? ( @ reviews ) do %>
193- < . card class = "text-center " >
194- < . card_header >
195- < div class = "mx-auto mb-2 rounded-full bg-muted p-4 " >
196- < . icon name = "tabler-contract " class = "h-8 w-8 text-muted-foreground " />
194+ <%= if Enum . empty? ( @ contributions ) do %>
195+ < h2 class = "text-lg font-semibold " > Completed Contracts</ h2 >
196+ <%= if Enum . empty? ( @ reviews ) do %>
197+ < . card class = "text-center " >
198+ < . card_header >
199+ < div class = "mx-auto mb-2 rounded-full bg-muted p-4 " >
200+ < . icon name = "tabler-contract " class = "h-8 w-8 text-muted-foreground " />
201+ </ div >
202+ < . card_title > No completed contracts yet</ . card_title >
203+ < . card_description >
204+ Contracts will appear here once completed
205+ </ . card_description >
206+ </ . card_header >
207+ </ . card >
208+ <% else %>
209+ <%= for review <- @ reviews do %>
210+ < div class = "w-full rounded-lg border border-border bg-card p-4 text-sm " >
211+ < div class = "mb-2 flex items-center gap-1 " >
212+ <%= for i <- 1 .. Review . max_rating ( ) do %>
213+ < . icon
214+ name = "tabler-star-filled "
215+ class = { "#{ if i <= review . rating , do: "text-foreground" , else: "text-muted-foreground/25" } h-4 w-4" }
216+ />
217+ <% end %>
218+ </ div >
219+ < p class = "mb-2 text-sm " > { review . content } </ p >
220+ < div class = "flex items-center gap-3 " >
221+ < . avatar class = "h-8 w-8 " >
222+ < . avatar_image src = { review . reviewer . avatar_url } alt = { review . reviewer . name } />
223+ < . avatar_fallback >
224+ { Algora.Util . initials ( review . reviewer . name ) }
225+ </ . avatar_fallback >
226+ </ . avatar >
227+ < div class = "flex flex-col " >
228+ < p class = "text-sm font-medium " > { review . reviewer . name } </ p >
229+ < p class = "text-xs text-muted-foreground " >
230+ { review . organization . name }
231+ </ p >
232+ </ div >
233+ </ div >
197234 </ div >
198- < . card_title > No completed contracts yet</ . card_title >
199- < . card_description >
200- Contracts will appear here once completed
201- </ . card_description >
202- </ . card_header >
203- </ . card >
235+ <% end %>
236+ <% end %>
204237 <% else %>
205- <%= for review <- @ reviews do %>
206- < div class = "w-full rounded-lg border border-border bg-card p-4 text-sm " >
207- < div class = "mb-2 flex items-center gap-1 " >
208- <%= for i <- 1 .. Review . max_rating ( ) do %>
209- < . icon
210- name = "tabler-star-filled "
211- class = { "#{ if i <= review . rating , do: "text-foreground" , else: "text-muted-foreground/25" } h-4 w-4" }
212- />
213- <% end %>
214- </ div >
215- < p class = "mb-2 text-sm " > { review . content } </ p >
216- < div class = "flex items-center gap-3 " >
217- < . avatar class = "h-8 w-8 " >
218- < . avatar_image src = { review . reviewer . avatar_url } alt = { review . reviewer . name } />
219- < . avatar_fallback >
220- { Algora.Util . initials ( review . reviewer . name ) }
221- </ . avatar_fallback >
222- </ . avatar >
223- < div class = "flex flex-col " >
224- < p class = "text-sm font-medium " > { review . reviewer . name } </ p >
225- < p class = "text-xs text-muted-foreground " >
226- { review . organization . name }
227- </ p >
238+ < h2 class = "text-lg font-semibold " > Top Contributions</ h2 >
239+ <%= for { owner , contributions } <- aggregate_contributions ( @ contributions ) do %>
240+ < . link
241+ href = { "https://github.com/#{ owner . provider_login } /#{ List . first ( contributions ) . repository . name } /pulls?q=author%3A#{ @ user . provider_login } +is%3Amerged+" }
242+ target = "_blank "
243+ rel = "noopener "
244+ class = "flex items-center gap-3 rounded-xl pr-2 bg-card/50 border border-border/50 hover:border-border transition-all "
245+ >
246+ < img
247+ src = { owner . avatar_url }
248+ class = "h-12 w-12 rounded-xl rounded-r-none transition-all "
249+ alt = { owner . name }
250+ />
251+ < div class = "w-full flex flex-col text-xs font-medium gap-0.5 " >
252+ < span class = "flex items-start justify-between gap-5 " >
253+ < span class = "font-display " >
254+ { if owner . type == :organization do
255+ owner . name
256+ else
257+ List . first ( contributions ) . repository . name
258+ end }
259+ </ span >
260+ <%= if tech = List . first ( List . first ( contributions ) . repository . tech_stack ) do %>
261+ < span class = "flex items-center text-foreground text-[11px] gap-1 " >
262+ < img
263+ src = { "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/#{ String . downcase ( tech ) } /#{ String . downcase ( tech ) } -original.svg" }
264+ class = "w-4 h-4 invert saturate-0 "
265+ /> { tech }
266+ </ span >
267+ <% end %>
268+ </ span >
269+ < div class = "flex items-center gap-2 font-semibold " >
270+ < span class = "flex items-center text-amber-300 text-xs " >
271+ < . icon name = "tabler-star-filled " class = "h-4 w-4 mr-1 " />
272+ { Algora.Util . format_number_compact (
273+ max ( owner . stargazers_count , total_stars ( contributions ) )
274+ ) }
275+ </ span >
276+ < span class = "flex items-center text-purple-400 text-xs " >
277+ < . icon name = "tabler-git-pull-request " class = "h-4 w-4 mr-1 " />
278+ { Algora.Util . format_number_compact ( total_contributions ( contributions ) ) }
279+ </ span >
228280 </ div >
229281 </ div >
230- </ div >
282+ </ . link >
231283 <% end %>
232284 <% end %>
233285 </ div >
@@ -276,4 +328,24 @@ defmodule AlgoraWeb.User.ProfileLive do
276328 )
277329 end )
278330 end
331+
332+ defp aggregate_contributions ( contributions ) do
333+ groups = Enum . group_by ( contributions , fn c -> c . repository . user end )
334+
335+ contributions
336+ |> Enum . map ( fn c -> { c . repository . user , groups [ c . repository . user ] } end )
337+ |> Enum . dedup_by ( fn { owner , _ } -> owner end )
338+ end
339+
340+ defp total_stars ( contributions ) do
341+ contributions
342+ |> Enum . map ( & & 1 . repository . stargazers_count )
343+ |> Enum . sum ( )
344+ end
345+
346+ defp total_contributions ( contributions ) do
347+ contributions
348+ |> Enum . map ( & & 1 . contribution_count )
349+ |> Enum . sum ( )
350+ end
279351end
0 commit comments