Skip to content

Commit 01c5aa6

Browse files
committed
feat: add community highlights to home page
1 parent f121bec commit 01c5aa6

File tree

1 file changed

+265
-65
lines changed

1 file changed

+265
-65
lines changed

lib/algora_web/live/home_live.ex

Lines changed: 265 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ defmodule AlgoraWeb.HomeLive do
66
import AlgoraWeb.Components.ModalVideo
77

88
alias Algora.Accounts
9+
alias Algora.Bounties
910
alias Algora.Jobs
11+
alias Algora.Payments
1012
alias AlgoraWeb.Components.Footer
1113
alias AlgoraWeb.Components.Header
1214
alias AlgoraWeb.Data.HomeCache
@@ -52,7 +54,8 @@ defmodule AlgoraWeb.HomeLive do
5254
|> assign(:company_people_examples, company_people_examples)
5355
|> assign(:show_challenge_drawer, false)
5456
|> assign(:challenge_form, to_form(ChallengeForm.changeset(%ChallengeForm{}, %{})))
55-
|> assign_user_applications()}
57+
|> assign_user_applications()
58+
|> assign_events()}
5659
end
5760
end
5861

@@ -276,70 +279,13 @@ defmodule AlgoraWeb.HomeLive do
276279
</section>
277280
278281
<section class="relative isolate pb-16 sm:pb-40">
279-
<div class="mx-auto max-w-7xl px-6 lg:px-8">
280-
<h2 class="font-display text-2xl sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl font-semibold tracking-tight text-foreground text-center mb-2">
281-
Open positions
282-
</h2>
283-
<p class="text-center text-muted-foreground mb-8">
284-
Discover jobs at top startups
285-
</p>
286-
287-
<%= if Enum.empty?(@jobs_by_user) do %>
288-
<div class="text-center py-12">
289-
<.icon name="tabler-briefcase" class="h-12 w-12 text-muted-foreground mx-auto mb-4" />
290-
<p class="text-muted-foreground">No open positions at the moment</p>
291-
</div>
292-
<% else %>
293-
<div class="flex flex-col sm:grid gap-6 max-w-4xl mx-auto">
294-
<%= for {user, jobs} <- Enum.take(@jobs_by_user, 3) do %>
295-
<%= for job <- Enum.take(jobs, 1) do %>
296-
<div class="flex flex-col sm:flex-row gap-4 justify-between p-6 bg-card rounded-xl border hover:shadow-lg transition-shadow">
297-
<div>
298-
<div class="flex items-start gap-4">
299-
<.avatar class="size-12">
300-
<.avatar_image src={user.avatar_url} />
301-
<.avatar_fallback>
302-
{Algora.Util.initials(user.name)}
303-
</.avatar_fallback>
304-
</.avatar>
305-
<div>
306-
<div class="font-semibold text-foreground">{job.title}</div>
307-
<div class="text-sm text-muted-foreground">{user.name}</div>
308-
</div>
309-
</div>
310-
<div class="flex gap-2 mt-2 sm:pl-12">
311-
<%= for tech <- Enum.take(job.tech_stack || [], 3) do %>
312-
<.tech_badge tech={tech} size="sm" />
313-
<% end %>
314-
</div>
315-
</div>
316-
<div>
317-
<%= if MapSet.member?(@user_applications, job.id) do %>
318-
<.button size="sm" disabled class="opacity-50 w-full sm:w-auto">
319-
Applied
320-
</.button>
321-
<% else %>
322-
<.button
323-
size="sm"
324-
phx-click="apply_job"
325-
phx-value-job-id={job.id}
326-
class="w-full sm:w-auto"
327-
>
328-
Apply
329-
</.button>
330-
<% end %>
331-
</div>
332-
</div>
333-
<% end %>
334-
<% end %>
335-
</div>
336-
337-
<div class="text-center mt-8">
338-
<.button navigate={~p"/jobs"} variant="outline">
339-
View all positions
340-
</.button>
341-
</div>
342-
<% end %>
282+
<div class="flex flex-col gap-4 px-4 pt-6 sm:pt-10 mx-auto max-w-4xl">
283+
<div class="mx-auto max-w-7xl px-6 lg:px-8 pt-24 xl:pt-0">
284+
<h2 class="font-display text-3xl font-semibold tracking-tight text-foreground sm:text-6xl mb-2 sm:mb-4">
285+
Community highlights
286+
</h2>
287+
</div>
288+
<.events events={@events} />
343289
</div>
344290
</section>
345291
@@ -697,6 +643,237 @@ defmodule AlgoraWeb.HomeLive do
697643
assign(socket, :user_applications, user_applications)
698644
end
699645

646+
defp events(assigns) do
647+
~H"""
648+
<ul class="w-full pl-10 relative space-y-8">
649+
<li :for={{event, index} <- @events |> Enum.with_index()} class="relative">
650+
<.event_item type={event.type} event={event} last?={index == length(@events) - 1} />
651+
</li>
652+
</ul>
653+
"""
654+
end
655+
656+
defp event_item(%{type: :transaction} = assigns) do
657+
assigns = assign(assigns, :transaction, assigns.event.item)
658+
659+
~H"""
660+
<div>
661+
<div class="relative -ml-[2.75rem]">
662+
<span
663+
:if={!@last?}
664+
class="absolute left-1 top-6 h-full w-0.5 block ml-[2.75rem] bg-muted-foreground/25"
665+
aria-hidden="true"
666+
>
667+
</span>
668+
<.link
669+
rel="noopener"
670+
target="_blank"
671+
class="w-full group inline-flex"
672+
href={
673+
if @transaction.ticket.repository,
674+
do: @transaction.ticket.url,
675+
else: ~p"/#{@transaction.linked_transaction.user.handle}/home"
676+
}
677+
>
678+
<div class="w-full relative flex space-x-3">
679+
<div class="w-full flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
680+
<div class="w-full flex items-center gap-3">
681+
<div class="flex -space-x-1 ring-8 ring-black">
682+
<span class="relative shrink-0 overflow-hidden flex h-9 w-9 sm:h-12 sm:w-12 items-center justify-center rounded-xl ring-4 bg-gray-950 ring-black">
683+
<img
684+
class="aspect-square h-full w-full"
685+
alt={@transaction.user.name}
686+
src={@transaction.user.avatar_url}
687+
/>
688+
</span>
689+
<span class="relative shrink-0 overflow-hidden flex h-9 w-9 sm:h-12 sm:w-12 items-center justify-center rounded-xl ring-4 bg-gray-950 ring-black">
690+
<img
691+
class="aspect-square h-full w-full"
692+
alt={@transaction.linked_transaction.user.name}
693+
src={@transaction.linked_transaction.user.avatar_url}
694+
/>
695+
</span>
696+
</div>
697+
<div class="w-full z-10 flex gap-3 items-start xl:items-end">
698+
<p class="text-xs transition-colors text-muted-foreground group-hover:text-foreground/90 sm:text-xl text-left">
699+
<span class="font-semibold text-foreground/80 group-hover:text-foreground transition-colors">
700+
{@transaction.linked_transaction.user.name}
701+
</span>
702+
awarded
703+
<span class="font-semibold text-foreground/80 group-hover:text-foreground transition-colors">
704+
{@transaction.user.name}
705+
</span>
706+
a
707+
<span class={
708+
classes([
709+
"font-bold font-display transition-colors",
710+
cond do
711+
@transaction.bounty_id && @transaction.ticket.repository ->
712+
"text-success-400 group-hover:text-success-300"
713+
714+
@transaction.bounty_id && !@transaction.ticket.repository ->
715+
"text-blue-400 group-hover:text-blue-300"
716+
717+
true ->
718+
"text-red-400 group-hover:text-red-300"
719+
end
720+
])
721+
}>
722+
{Money.to_string!(@transaction.net_amount)}
723+
<%= if @transaction.bounty_id do %>
724+
<%= if @transaction.ticket.repository do %>
725+
bounty
726+
<% else %>
727+
contract
728+
<% end %>
729+
<% else %>
730+
tip
731+
<% end %>
732+
</span>
733+
</p>
734+
<div class="ml-auto xl:ml-0 xl:mb-[2px] whitespace-nowrap text-xs text-muted-foreground sm:text-sm">
735+
<time datetime={@transaction.succeeded_at}>
736+
{cond do
737+
@transaction.bounty_id && !@transaction.ticket.repository ->
738+
start_month = Calendar.strftime(@transaction.succeeded_at, "%B")
739+
end_date = Date.add(@transaction.succeeded_at, 30)
740+
end_month = Calendar.strftime(end_date, "%B")
741+
742+
if start_month == end_month do
743+
"#{start_month} #{Calendar.strftime(end_date, "%Y")}"
744+
else
745+
"#{start_month} - #{end_month} #{Calendar.strftime(end_date, "%Y")}"
746+
end
747+
748+
true ->
749+
Algora.Util.time_ago(@transaction.succeeded_at)
750+
end}
751+
</time>
752+
</div>
753+
</div>
754+
</div>
755+
</div>
756+
</div>
757+
</.link>
758+
</div>
759+
</div>
760+
"""
761+
end
762+
763+
defp event_item(%{type: :job} = assigns) do
764+
assigns = assign(assigns, :job, assigns.event.item)
765+
766+
~H"""
767+
<div :if={@job.user.handle}>
768+
<div class="relative -ml-[2.75rem]">
769+
<span
770+
:if={!@last?}
771+
class="absolute left-1 top-6 h-full w-0.5 block ml-[2.75rem] bg-muted-foreground/25"
772+
aria-hidden="true"
773+
>
774+
</span>
775+
<.link
776+
rel="noopener"
777+
target="_blank"
778+
class="w-full group inline-flex"
779+
href={~p"/#{@job.user.handle}/jobs"}
780+
>
781+
<div class="w-full relative flex space-x-3">
782+
<div class="w-full flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
783+
<div class="w-full flex items-center gap-3">
784+
<div class="flex -space-x-1 ring-8 ring-black">
785+
<span class="ml-6 relative shrink-0 overflow-hidden flex h-9 w-9 sm:h-12 sm:w-12 items-center justify-center rounded-xl">
786+
<img
787+
class="aspect-square h-full w-full"
788+
alt={@job.user.name}
789+
src={@job.user.avatar_url}
790+
/>
791+
</span>
792+
</div>
793+
<div class="w-full z-10 flex gap-3 items-start xl:items-end">
794+
<p class="text-xs transition-colors text-muted-foreground group-hover:text-foreground/90 sm:text-xl text-left">
795+
<span class="font-semibold text-foreground/80 group-hover:text-foreground transition-colors">
796+
{@job.user.name}
797+
</span>
798+
is hiring!
799+
<span class="font-semibold text-purple-400 group-hover:text-purple-300 transition-colors">
800+
{@job.title}
801+
</span>
802+
</p>
803+
<div class="ml-auto xl:ml-0 xl:mb-[2px] whitespace-nowrap text-xs text-muted-foreground sm:text-sm">
804+
<time datetime={@job.inserted_at}>
805+
{Algora.Util.time_ago(@job.inserted_at)}
806+
</time>
807+
</div>
808+
</div>
809+
</div>
810+
</div>
811+
</div>
812+
</.link>
813+
</div>
814+
</div>
815+
"""
816+
end
817+
818+
defp event_item(%{type: :bounty} = assigns) do
819+
assigns = assign(assigns, :bounty, assigns.event.item)
820+
821+
~H"""
822+
<div>
823+
<div class="relative -ml-[2.75rem]">
824+
<span
825+
:if={!@last?}
826+
class="absolute left-1 top-6 h-full w-0.5 block ml-[2.75rem] bg-muted-foreground/25"
827+
aria-hidden="true"
828+
>
829+
</span>
830+
<.link
831+
rel="noopener"
832+
target="_blank"
833+
class="w-full group inline-flex"
834+
href={
835+
if @bounty.repository,
836+
do: @bounty.ticket.url,
837+
else: ~p"/#{@bounty.owner.handle}/home"
838+
}
839+
>
840+
<div class="w-full relative flex space-x-3">
841+
<div class="w-full flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
842+
<div class="w-full flex items-center gap-3">
843+
<div class="flex -space-x-1 ring-8 ring-black">
844+
<span class="ml-6 relative shrink-0 overflow-hidden flex h-9 w-9 sm:h-12 sm:w-12 items-center justify-center rounded-xl bg-gray-950">
845+
<img
846+
class="aspect-square h-full w-full"
847+
alt={@bounty.owner.name}
848+
src={@bounty.owner.avatar_url}
849+
/>
850+
</span>
851+
</div>
852+
<div class="w-full z-10 flex gap-3 items-start xl:items-end">
853+
<p class="text-xs transition-colors text-muted-foreground group-hover:text-foreground/90 sm:text-xl text-left">
854+
<span class="font-semibold text-foreground/80 group-hover:text-foreground transition-colors">
855+
{@bounty.owner.name}
856+
</span>
857+
shared a
858+
<span class="font-bold font-display transition-colors text-cyan-400 group-hover:text-cyan-300">
859+
{Money.to_string!(@bounty.amount)} bounty
860+
</span>
861+
</p>
862+
<div class="ml-auto xl:ml-0 xl:mb-[2px] whitespace-nowrap text-xs text-muted-foreground sm:text-sm">
863+
<time datetime={@bounty.inserted_at}>
864+
{Algora.Util.time_ago(@bounty.inserted_at)}
865+
</time>
866+
</div>
867+
</div>
868+
</div>
869+
</div>
870+
</div>
871+
</.link>
872+
</div>
873+
</div>
874+
"""
875+
end
876+
700877
defp yc_logo_cloud(assigns) do
701878
~H"""
702879
<div>
@@ -1024,4 +1201,27 @@ defmodule AlgoraWeb.HomeLive do
10241201
</.drawer>
10251202
"""
10261203
end
1204+
1205+
defp assign_events(socket) do
1206+
transactions = Payments.list_featured_transactions()
1207+
bounties = Bounties.list_bounties(status: :open, limit: 10)
1208+
jobs_by_user = []
1209+
1210+
events =
1211+
transactions
1212+
|> Enum.map(fn tx -> %{item: tx, type: :transaction, timestamp: tx.succeeded_at} end)
1213+
|> Enum.concat(
1214+
jobs_by_user
1215+
|> Enum.flat_map(fn {_user, jobs} -> jobs end)
1216+
|> Enum.map(fn job -> %{item: job, type: :job, timestamp: job.inserted_at} end)
1217+
)
1218+
|> Enum.concat(
1219+
Enum.map(bounties || [], fn bounty ->
1220+
%{item: bounty, type: :bounty, timestamp: bounty.inserted_at}
1221+
end)
1222+
)
1223+
|> Enum.sort_by(& &1.timestamp, {:desc, DateTime})
1224+
1225+
assign(socket, :events, events)
1226+
end
10271227
end

0 commit comments

Comments
 (0)