Skip to content

Commit 6d6bd05

Browse files
fix: profile picture upload
1 parent 4fd7220 commit 6d6bd05

File tree

7 files changed

+159
-238
lines changed

7 files changed

+159
-238
lines changed

lib/atomic/accounts.ex

+2-1
Original file line numberDiff line numberDiff line change
@@ -500,10 +500,11 @@ defmodule Atomic.Accounts do
500500
{:error, %Ecto.Changeset{}}
501501
502502
"""
503-
def update_user(%User{} = user, attrs \\ %{}, _after_save \\ &{:ok, &1}) do
503+
def update_user(%User{} = user, attrs \\ %{}, after_save \\ &{:ok, &1}) do
504504
user
505505
|> User.changeset(attrs)
506506
|> Repo.update()
507+
|> after_save(after_save)
507508
end
508509

509510
@doc """

lib/atomic/accounts/user.ex

-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ defmodule Atomic.Accounts.User do
7171

7272
def picture_changeset(user, attrs) do
7373
user
74-
|> cast(attrs, @required_fields ++ @optional_fields)
7574
|> cast_attachments(attrs, [:profile_picture])
7675
end
7776

lib/atomic_web/components/sidebar.ex

+1-9
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ defmodule AtomicWeb.Components.Sidebar do
122122
<AtomicWeb.Components.Dropdown.dropdown orientation={@orientation} items={dropdown_items(@current_user)} id="user-menu-button">
123123
<:wrapper>
124124
<button class="flex w-full select-none flex-row items-center gap-x-2 px-4 py-3 text-sm font-semibold leading-6 text-zinc-700 lg:px-0">
125-
<AtomicWeb.Components.Avatar.avatar name={@current_user.name} src={user_image(@current_user)} size={:xs} color={:light_gray} class="!text-sm" />
125+
<AtomicWeb.Components.Avatar.avatar name={@current_user.name} src={Uploaders.ProfilePicture.url({@current_user.profile_picture, @current_user}, :original)} size={:xs} color={:light_gray} class="!text-sm" />
126126
<span class="text-sm font-semibold leading-6" aria-hidden="true"><%= @current_user.name %></span>
127127
<.icon name={:chevron_right} solid class="h-5 w-5" />
128128
</button>
@@ -188,14 +188,6 @@ defmodule AtomicWeb.Components.Sidebar do
188188
|> JS.dispatch("focus", to: "#mobile-sidebar")
189189
end
190190

191-
defp user_image(user) do
192-
if user.profile_picture do
193-
Uploaders.ProfilePicture.url({user, user.profile_picture}, :original)
194-
else
195-
nil
196-
end
197-
end
198-
199191
defp get_organizations(nil), do: []
200192
defp get_organizations(user), do: Organizations.list_user_organizations(user.id)
201193
end

lib/atomic_web/live/profile_live/form_component.ex

+148-67
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,149 @@ defmodule AtomicWeb.ProfileLive.FormComponent do
33

44
alias Atomic.Accounts
55
alias Atomic.Socials
6+
alias AtomicWeb.Components.ImageUploader
67

78
@extensions_whitelist ~w(.jpg .jpeg .gif .png)
89

10+
@impl true
11+
def render(assigns) do
12+
~H"""
13+
<div class="px-4 pt-4">
14+
<.form :let={f} for={@changeset} id="profile-form" phx-target={@myself} phx-change="validate" phx-submit="save">
15+
<!-- Upload de Foto -->
16+
<div class="flex flex-wrap justify-between">
17+
<!-- Secção para a imagem de capa -->
18+
<div class="flex-1 max-w-[70%] pr-4">
19+
<%= label(f, :cover_image, "Cover Image", class: "mt-3 mb-1 text-sm font-medium text-gray-700") %>
20+
</div>
21+
<!-- Secção para a imagem de perfil -->
22+
<div class="flex flex-shrink-0 flex-col items-center">
23+
<%= label(f, :name, "Profile Picture", class: "mt-3 mb-1 text-sm font-medium text-gray-700") %>
24+
<.live_component module={ImageUploader} id="uploader-profile-picture" uploads={@uploads} target={@myself} />
25+
</div>
26+
</div>
27+
<!-- Linha de separação -->
28+
<hr class="mb-6 h-full w-full border-gray-200" />
29+
<!-- Campos do formulário em duas colunas -->
30+
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
31+
<div>
32+
<%= label(f, :name, "Full name", class: "mb-1 text-sm font-medium text-gray-700") %>
33+
<%= text_input(f, :name,
34+
required: true,
35+
placeholder: gettext("John Doe"),
36+
class: "w-full appearance-none rounded border border-zinc-300 px-3 py-2 text-zinc-900 placeholder-zinc-500 focus:z-10 focus:border-orange-400 focus:outline-none focus:ring-orange-400 sm:text-sm"
37+
) %>
38+
<div class="text-sm text-red-600"><%= error_tag(f, :name) %></div>
39+
</div>
40+
41+
<div>
42+
<%= label(f, :email, "Email", class: "mb-1 text-sm font-medium text-gray-700") %>
43+
<%= text_input(f, :email,
44+
required: true,
45+
placeholder: gettext("john_doe@mail.com"),
46+
class: "w-full appearance-none rounded border border-zinc-300 px-3 py-2 text-zinc-900 placeholder-zinc-500 focus:z-10 focus:border-orange-400 focus:outline-none focus:ring-orange-400 sm:text-sm"
47+
) %>
48+
<div class="text-sm text-red-600"><%= error_tag(f, :email) %></div>
49+
</div>
50+
51+
<div>
52+
<%= label(f, :username, "Username", class: "mb-1 text-sm font-medium text-gray-700") %>
53+
<div class="relative flex w-full appearance-none rounded border border-zinc-300 px-3 text-zinc-900 placeholder-zinc-500 focus-within:z-10 focus-within:border-orange-400 focus-within:outline-none focus-within:ring-orange-400 sm:text-sm">
54+
<span class="select-none self-center">@</span>
55+
<%= text_input(f, :slug,
56+
required: true,
57+
spellcheck: false,
58+
placeholder: gettext("john_doe"),
59+
class: "w-full appearance-none border-none pl-0 text-zinc-900 placeholder-zinc-500 focus:outline-none focus:ring-transparent sm:text-sm"
60+
) %>
61+
</div>
62+
<div class="text-sm text-red-600"><%= error_tag(f, :slug) %></div>
63+
</div>
64+
65+
<div>
66+
<%= label(f, :phone_number, "Phone number", class: "mb-1 text-sm font-medium text-gray-700") %>
67+
<%= text_input(f, :phone_number,
68+
required: true,
69+
placeholder: gettext("912345678"),
70+
class: "w-full appearance-none rounded border border-zinc-300 px-3 py-2 text-zinc-900 placeholder-zinc-500 focus:z-10 focus:border-orange-400 focus:outline-none focus:ring-orange-400 sm:text-sm"
71+
) %>
72+
<div class="text-sm text-red-600"><%= error_tag(f, :phone_number) %></div>
73+
</div>
74+
75+
<div>
76+
<%= label(f, :bio, "Bio", class: "mb-1 text-sm font-medium text-gray-700") %>
77+
<%= textarea(f, :bio,
78+
placeholder: "Tell us about yourself",
79+
class: "w-full appearance-none rounded border border-zinc-300 px-3 py-2 text-zinc-900 placeholder-zinc-500 focus:z-10 focus:border-orange-400 focus:outline-none focus:ring-orange-400 sm:text-sm"
80+
) %>
81+
<div class="text-sm text-red-600"><%= error_tag(f, :bio) %></div>
82+
</div>
83+
84+
<div>
85+
<%= label(f, :instagram, "Instagram Username", class: "mb-1 text-sm font-medium text-gray-700") %>
86+
<%= text_input(f, :instagram,
87+
placeholder: "your_username",
88+
class: "w-full appearance-none rounded border border-zinc-300 px-3 py-2 text-zinc-900 placeholder-zinc-500 focus:z-10 focus:border-orange-400 focus:outline-none focus:ring-orange-400 sm:text-sm"
89+
) %>
90+
<div class="text-sm text-red-600"><%= error_tag(f, :instagram) %></div>
91+
</div>
92+
93+
<div>
94+
<%= label(f, :facebook, "Facebook Username", class: "mb-1 text-sm font-medium text-gray-700") %>
95+
<%= text_input(f, :facebook,
96+
placeholder: "your_username",
97+
class: "w-full appearance-none rounded border border-zinc-300 px-3 py-2 text-zinc-900 placeholder-zinc-500 focus:z-10 focus:border-orange-400 focus:outline-none focus:ring-orange-400 sm:text-sm"
98+
) %>
99+
<div class="text-sm text-red-600"><%= error_tag(f, :facebook) %></div>
100+
</div>
101+
102+
<div>
103+
<%= label(f, :twitter, "X Username", class: "mb-1 text-sm font-medium text-gray-700") %>
104+
<%= text_input(f, :twitter,
105+
placeholder: "your_username",
106+
class: "w-full appearance-none rounded border border-zinc-300 px-3 py-2 text-zinc-900 placeholder-zinc-500 focus:z-10 focus:border-orange-400 focus:outline-none focus:ring-orange-400 sm:text-sm"
107+
) %>
108+
<div class="text-sm text-red-600"><%= error_tag(f, :twitter) %></div>
109+
</div>
110+
111+
<div>
112+
<%= label(f, :tiktok, "TikTok Username", class: "mb-1 text-sm font-medium text-gray-700") %>
113+
<%= text_input(f, :tiktok,
114+
placeholder: "your_username",
115+
class: "w-full appearance-none rounded border border-zinc-300 px-3 py-2 text-zinc-900 placeholder-zinc-500 focus:z-10 focus:border-orange-400 focus:outline-none focus:ring-orange-400 sm:text-sm"
116+
) %>
117+
<div class="text-sm text-red-600"><%= error_tag(f, :tiktok) %></div>
118+
</div>
119+
</div>
120+
<!-- Botões -->
121+
<div class="mt-8 flex w-full justify-end">
122+
<button type="submit" class="inline-flex rounded-md border-2 border-orange-500 bg-white px-6 py-2 text-sm font-medium text-orange-500 shadow-sm hover:bg-orange-600 hover:text-white">
123+
Save
124+
</button>
125+
</div>
126+
</.form>
127+
</div>
128+
"""
129+
end
130+
9131
@impl true
10132
def mount(socket) do
11133
{:ok,
12134
socket
13-
|> allow_upload(:picture, accept: @extensions_whitelist, max_entries: 1)
14-
|> allow_upload(:cover_image, accept: @extensions_whitelist, max_entries: 1)}
135+
|> allow_upload(:image,
136+
accept: Uploaders.ProfilePicture.extension_whitelist(),
137+
max_entries: 1
138+
)}
15139
end
16140

17141
@impl true
18142
def update(%{user: user} = assigns, socket) do
19-
user_changeset = Accounts.change_user(user)
20-
socials_changeset = Socials.changeset(%Socials{}, %{})
21-
22-
combined_changeset = %{
23-
user_changeset
24-
| changes: Map.merge(user_changeset.changes, socials_changeset.changes)
25-
}
143+
changeset = Accounts.change_user(user)
26144

27145
{:ok,
28146
socket
29147
|> assign(assigns)
30-
|> assign(:changeset, combined_changeset)}
148+
|> assign(:changeset, changeset)}
31149
end
32150

33151
@impl true
@@ -41,41 +159,31 @@ defmodule AtomicWeb.ProfileLive.FormComponent do
41159
end
42160

43161
@impl true
44-
def handle_event("save", %{"user" => user_params, "socials" => socials_params}, socket) do
162+
def handle_event("save", %{"user" => user_params}, socket) do
45163
user = socket.assigns.user
46164

47-
socials_changeset = Socials.changeset(%Socials{}, socials_params)
48-
49-
case socials_changeset.valid? do
50-
true ->
51-
:ok
52-
53-
false ->
54-
{:noreply, assign(socket, :changeset, socials_changeset)}
55-
end
165+
flash_text =
166+
if user_params["email"] != user.email do
167+
case Accounts.apply_user_email(user, %{email: user_params["email"]}) do
168+
{:ok, applied_user} ->
169+
Accounts.deliver_update_email_instructions(
170+
applied_user,
171+
user.email,
172+
&Routes.profile_edit_url(socket, :confirm_email, &1)
173+
)
174+
175+
"Profile updated successfully, please check your email to confirm the new address."
176+
end
177+
else
178+
"Profile updated successfully."
179+
end
56180

57181
case Accounts.update_user(
58182
user,
59183
Map.put(user_params, "email", user.email),
60184
&consume_image_data(socket, &1)
61185
) do
62186
{:ok, _user} ->
63-
flash_text =
64-
if user_params["email"] != user.email do
65-
case Accounts.apply_user_email(user, %{email: user_params["email"]}) do
66-
{:ok, applied_user} ->
67-
Accounts.deliver_update_email_instructions(
68-
applied_user,
69-
user.email,
70-
&Routes.profile_edit_url(socket, :confirm_email, &1)
71-
)
72-
73-
"Profile updated successfully, please check your email to confirm the new address."
74-
end
75-
else
76-
"Profile updated successfully."
77-
end
78-
79187
{:noreply,
80188
socket
81189
|> put_flash(:success, flash_text)
@@ -87,26 +195,12 @@ defmodule AtomicWeb.ProfileLive.FormComponent do
87195
end
88196

89197
defp consume_image_data(socket, user) do
90-
consume_uploaded_entries(socket, :picture, fn %{path: path}, entry ->
91-
resized_path = resize_image(path, 200, 200)
92-
93-
Accounts.update_user(user, %{
94-
"picture" => %Plug.Upload{
198+
consume_uploaded_entries(socket, :image, fn %{path: path}, entry ->
199+
Accounts.update_user_picture(user, %{
200+
"profile_picture" => %Plug.Upload{
95201
content_type: entry.client_type,
96202
filename: entry.client_name,
97-
path: resized_path
98-
}
99-
})
100-
end)
101-
102-
consume_uploaded_entries(socket, :cover_image, fn %{path: path}, entry ->
103-
resized_path = resize_image(path, 800, 250)
104-
105-
Accounts.update_user(user, %{
106-
"cover_image" => %Plug.Upload{
107-
content_type: entry.client_type,
108-
filename: entry.client_name,
109-
path: resized_path
203+
path: path
110204
}
111205
})
112206
end)
@@ -118,17 +212,4 @@ defmodule AtomicWeb.ProfileLive.FormComponent do
118212
{:ok, user}
119213
end
120214
end
121-
122-
defp resize_image(path, width, height) do
123-
command = "convert"
124-
args = ["-resize", "#{width}x#{height}", path, path]
125-
126-
case System.cmd(command, args) do
127-
{_, 0} ->
128-
{:ok, path}
129-
130-
{error_msg, _} ->
131-
{:error, error_msg}
132-
end
133-
end
134215
end

0 commit comments

Comments
 (0)