Skip to content

Commit 1b226e4

Browse files
committed
feat: user media for jobs pages
1 parent b8f23a9 commit 1b226e4

File tree

6 files changed

+178
-95
lines changed

6 files changed

+178
-95
lines changed

lib/algora/accounts/accounts.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule Algora.Accounts do
55

66
alias Algora.Accounts.Identity
77
alias Algora.Accounts.User
8+
alias Algora.Accounts.UserMedia
89
alias Algora.Bounties.Bounty
910
alias Algora.Contracts.Contract
1011
alias Algora.Github
@@ -740,4 +741,18 @@ defmodule Algora.Accounts do
740741

741742
Algora.Mailer.deliver(email)
742743
end
744+
745+
def list_user_media(%User{} = user) do
746+
Repo.all(from m in UserMedia, where: m.user_id == ^user.id)
747+
end
748+
749+
def create_user_media(%User{} = user, attrs) do
750+
%UserMedia{}
751+
|> UserMedia.changeset(Map.put(attrs, "user_id", user.id))
752+
|> Repo.insert()
753+
end
754+
755+
def delete_user_media(%UserMedia{} = media) do
756+
Repo.delete(media)
757+
end
743758
end

lib/algora/accounts/schemas/user.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule Algora.Accounts.User do
44

55
alias Algora.Accounts.Identity
66
alias Algora.Accounts.User
7+
alias Algora.Accounts.UserMedia
78
alias Algora.Activities.Activity
89
alias Algora.Bounties.Bounty
910
alias Algora.Bounties.Tip
@@ -109,6 +110,8 @@ defmodule Algora.Accounts.User do
109110

110111
has_one :customer, Algora.Payments.Customer, foreign_key: :user_id
111112

113+
has_many :media, UserMedia
114+
112115
timestamps()
113116
end
114117

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
defmodule Algora.Accounts.UserMedia do
2+
@moduledoc false
3+
use Algora.Schema
4+
5+
alias Algora.Accounts.User
6+
7+
typed_schema "user_media" do
8+
field :url, :string
9+
10+
belongs_to :user, User
11+
12+
timestamps()
13+
end
14+
15+
def changeset(user_media, attrs) do
16+
user_media
17+
|> cast(attrs, [:url, :user_id])
18+
|> validate_required([:url, :user_id])
19+
|> generate_id()
20+
|> foreign_key_constraint(:user_id)
21+
end
22+
end

lib/algora_web/live/org/jobs_live.ex

Lines changed: 114 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ defmodule AlgoraWeb.Org.JobsLive do
1010

1111
@impl true
1212
def mount(_params, _session, socket) do
13-
jobs_by_user = Enum.group_by(Jobs.list_jobs(user_id: socket.assigns.current_org.id), & &1.user)
13+
jobs = Jobs.list_jobs(user_id: socket.assigns.current_org.id)
14+
media = Accounts.list_user_media(socket.assigns.current_org)
1415

1516
{:ok,
1617
socket
1718
|> assign(:page_title, "Jobs")
18-
|> assign(:jobs_by_user, jobs_by_user)
19+
|> assign(:jobs, jobs)
20+
|> assign(:media, media)
1921
|> assign_user_applications()}
2022
end
2123

@@ -32,24 +34,73 @@ defmodule AlgoraWeb.Org.JobsLive do
3234
<div class={
3335
classes([
3436
"mx-auto max-w-7xl px-4 md:px-6 lg:px-8",
35-
if(!@current_user, do: "py-16 sm:py-24", else: "py-4 md:py-6 lg:py-8")
37+
if(!@current_user, do: "py-8", else: "py-4 md:py-6 lg:py-8")
3638
])
3739
}>
3840
<div class={
3941
classes([
4042
if(!@current_user, do: "text-center", else: "")
4143
])
4244
}>
43-
<h2 class="font-display text-3xl font-semibold tracking-tight text-foreground sm:text-6xl mb-2">
44-
Join {@current_org.name}
45+
<div class="flex items-start md:items-center justify-center gap-4">
46+
<.avatar class="h-16 w-16">
47+
<.avatar_image src={@current_org.avatar_url} />
48+
<.avatar_fallback>
49+
{Algora.Util.initials(@current_org.name)}
50+
</.avatar_fallback>
51+
</.avatar>
52+
<%!-- <div>
53+
<div class="text-lg text-foreground font-bold font-display">
54+
{@current_org.name}
55+
</div>
56+
<div class="text-sm text-muted-foreground line-clamp-2 md:line-clamp-1">
57+
{@current_org.bio}
58+
</div>
59+
<div class="flex gap-2 items-center">
60+
<%= for {platform, icon} <- social_icons(),
61+
url = social_link(@current_org, platform),
62+
not is_nil(url) do %>
63+
<.link
64+
href={url}
65+
target="_blank"
66+
class="text-muted-foreground hover:text-foreground"
67+
>
68+
<.icon name={icon} class="size-4" />
69+
</.link>
70+
<% end %>
71+
</div>
72+
</div> --%>
73+
</div>
74+
75+
<h2 class="pt-4 font-display text-3xl font-semibold tracking-tight text-foreground sm:text-6xl mb-2">
76+
Engineering at {@current_org.name}
4577
</h2>
46-
<p class="font-medium text-base text-muted-foreground">
47-
Open positions at {@current_org.name}
78+
<p class="pt-1 font-medium text-base text-muted-foreground">
79+
Open software engineering positions at {@current_org.name}
4880
</p>
81+
<div class="pt-2 flex gap-2 items-center justify-center">
82+
<%= for {platform, icon} <- social_icons(),
83+
url = social_link(@current_org, platform),
84+
not is_nil(url) do %>
85+
<.link href={url} target="_blank" class="text-muted-foreground hover:text-foreground">
86+
<.icon name={icon} class="size-5" />
87+
</.link>
88+
<% end %>
89+
</div>
4990
</div>
5091
92+
<%= if not Enum.empty?(@media) do %>
93+
<div class="mt-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
94+
<%= for media <- @media |> Enum.take(3) do %>
95+
<div class="aspect-video w-full rounded-lg overflow-hidden">
96+
<img src={media.url} class="w-full h-full object-cover object-center" />
97+
</div>
98+
<% end %>
99+
</div>
100+
<% end %>
101+
51102
<.section class="pt-8">
52-
<%= if Enum.empty?(@jobs_by_user) do %>
103+
<%= if Enum.empty?(@jobs) do %>
53104
<.card class="rounded-lg bg-card py-12 text-center lg:rounded-[2rem]">
54105
<.card_header>
55106
<div class="mx-auto mb-2 rounded-full bg-muted p-4">
@@ -63,99 +114,67 @@ defmodule AlgoraWeb.Org.JobsLive do
63114
</.card>
64115
<% else %>
65116
<div class="grid gap-12">
66-
<%= for {user, jobs} <- @jobs_by_user do %>
67-
<.card class="flex flex-col p-6">
68-
<div class="flex items-start md:items-center gap-4">
69-
<.avatar class="h-16 w-16">
70-
<.avatar_image src={user.avatar_url} />
71-
<.avatar_fallback>
72-
{Algora.Util.initials(user.name)}
73-
</.avatar_fallback>
74-
</.avatar>
75-
<div>
76-
<div class="text-lg text-foreground font-bold font-display">
77-
{user.name}
78-
</div>
79-
<div class="text-sm text-muted-foreground line-clamp-2 md:line-clamp-1">
80-
{user.bio}
81-
</div>
82-
<div class="flex gap-2 items-center">
83-
<%= for {platform, icon} <- social_icons(),
84-
url = social_link(user, platform),
85-
not is_nil(url) do %>
86-
<.link
87-
href={url}
88-
target="_blank"
89-
class="text-muted-foreground hover:text-foreground"
90-
>
91-
<.icon name={icon} class="size-4" />
92-
</.link>
93-
<% end %>
94-
</div>
95-
</div>
96-
</div>
97-
98-
<div class="pt-8 grid gap-8">
99-
<%= for job <- jobs do %>
100-
<div class="flex flex-col md:flex-row justify-between gap-4">
117+
<.card class="flex flex-col p-6">
118+
<div class="pt-8 grid gap-8">
119+
<%= for job <- @jobs do %>
120+
<div class="flex flex-col md:flex-row justify-between gap-4">
121+
<div>
101122
<div>
102-
<div>
103-
<%= if @current_user_role in [:admin, :mod] do %>
104-
<.link
105-
navigate={~p"/org/#{@current_org.handle}/jobs/#{job.id}"}
106-
class="text-lg font-semibold hover:underline"
107-
>
108-
{job.title}
109-
</.link>
110-
<% else %>
111-
<div class="text-lg font-semibold">
112-
{job.title}
113-
</div>
114-
<% end %>
115-
</div>
116-
<div
117-
:if={job.description}
118-
class="pt-1 text-sm text-muted-foreground prose prose-invert max-w-none"
119-
>
120-
<div
121-
id={"job-description-#{job.id}"}
122-
class="line-clamp-3 transition-all duration-200"
123-
phx-hook="ExpandableText"
124-
data-expand-id={"expand-#{job.id}"}
125-
data-class="line-clamp-3"
123+
<%= if @current_user_role in [:admin, :mod] do %>
124+
<.link
125+
navigate={~p"/org/#{@current_org.handle}/jobs/#{job.id}"}
126+
class="text-lg font-semibold hover:underline"
126127
>
127-
{Phoenix.HTML.raw(Markdown.render(job.description))}
128+
{job.title}
129+
</.link>
130+
<% else %>
131+
<div class="text-lg font-semibold">
132+
{job.title}
128133
</div>
129-
<button
130-
id={"expand-#{job.id}"}
131-
type="button"
132-
class="text-xs text-foreground font-bold mt-2 hidden"
133-
data-content-id={"job-description-#{job.id}"}
134-
phx-hook="ExpandableTextButton"
135-
>
136-
...see more
137-
</button>
138-
</div>
139-
<div class="pt-2 flex flex-wrap gap-2">
140-
<%= for tech <- job.tech_stack do %>
141-
<.badge variant="outline">{tech}</.badge>
142-
<% end %>
134+
<% end %>
135+
</div>
136+
<div
137+
:if={job.description}
138+
class="pt-1 text-sm text-muted-foreground prose prose-invert max-w-none"
139+
>
140+
<div
141+
id={"job-description-#{job.id}"}
142+
class="line-clamp-3 transition-all duration-200"
143+
phx-hook="ExpandableText"
144+
data-expand-id={"expand-#{job.id}"}
145+
data-class="line-clamp-3"
146+
>
147+
{Phoenix.HTML.raw(Markdown.render(job.description))}
143148
</div>
149+
<button
150+
id={"expand-#{job.id}"}
151+
type="button"
152+
class="text-xs text-foreground font-bold mt-2 hidden"
153+
data-content-id={"job-description-#{job.id}"}
154+
phx-hook="ExpandableTextButton"
155+
>
156+
...see more
157+
</button>
158+
</div>
159+
<div class="pt-2 flex flex-wrap gap-2">
160+
<%= for tech <- job.tech_stack do %>
161+
<.badge variant="outline">{tech}</.badge>
162+
<% end %>
144163
</div>
145-
<%= if MapSet.member?(@user_applications, job.id) do %>
146-
<.button disabled class="opacity-50">
147-
<.icon name="tabler-check" class="h-4 w-4 mr-2 -ml-1" /> Applied
148-
</.button>
149-
<% else %>
150-
<.button phx-click="apply_job" phx-value-job-id={job.id}>
151-
<.icon name="github" class="h-4 w-4 mr-2" /> Apply with GitHub
152-
</.button>
153-
<% end %>
154164
</div>
155-
<% end %>
156-
</div>
157-
</.card>
158-
<% end %>
165+
<%= if MapSet.member?(@user_applications, job.id) do %>
166+
<.button disabled class="opacity-50">
167+
<.icon name="tabler-check" class="h-4 w-4 mr-2 -ml-1" /> Applied
168+
</.button>
169+
<% else %>
170+
<.button phx-click="apply_job" phx-value-job-id={job.id}>
171+
<.icon name="github" class="h-4 w-4 mr-2" /> Apply with GitHub
172+
</.button>
173+
<% end %>
174+
</div>
175+
<% end %>
176+
</div>
177+
</.card>
159178
</div>
160179
<% end %>
161180
</.section>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
defmodule Algora.Repo.Migrations.CreateUserMedia do
2+
use Ecto.Migration
3+
4+
def change do
5+
create table(:user_media) do
6+
add :url, :string, null: false
7+
add :user_id, references(:users, on_delete: :delete_all), null: false
8+
9+
timestamps()
10+
end
11+
12+
create index(:user_media, [:user_id])
13+
end
14+
end
Lines changed: 10 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)