Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: profile pages #528

Draft
wants to merge 26 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
64f8726
Update 22/23 Team (#249)
ruioliveira02 Oct 21, 2022
ef699a9
Update Felicio picture (#250)
feliciofilipe Oct 21, 2022
e1f06cd
Add partners (#282)
MarioRodrigues10 Apr 22, 2023
99b28b4
feat: initial profile page
AfonsoMartins26 Sep 23, 2024
ea9e7cc
fix: save redirect
AfonsoMartins26 Sep 23, 2024
7063235
Fix: save redirect Tiktok
AfonsoMartins26 Sep 24, 2024
90093c9
Chore: improve layout of profile page
AfonsoMartins26 Sep 24, 2024
0bab33d
Feat: Improve html
AfonsoMartins26 Sep 24, 2024
7cc2a00
misspell
AfonsoMartins26 Sep 24, 2024
95cbd6a
Fix: Profile Picture upload
AfonsoMartins26 Sep 25, 2024
ad5460f
format code
AfonsoMartins26 Sep 26, 2024
21741f0
Merge branch 'develop' into am/profile
AfonsoMartins26 Sep 28, 2024
07b3111
feat: preview profile image in edit page
AfonsoMartins26 Oct 1, 2024
d44469c
Merge branch 'am/profile' of github.com:cesium/atomic into am/profile
AfonsoMartins26 Oct 1, 2024
da8d768
fix: miss div
AfonsoMartins26 Oct 1, 2024
1854bf2
feat: add slug field
AfonsoMartins26 Oct 3, 2024
e9c9aec
fix: delete IO
AfonsoMartins26 Oct 3, 2024
85b8d58
feat: pass checks
AfonsoMartins26 Oct 3, 2024
95b9214
merge with main
AfonsoMartins26 Jan 9, 2025
4a053dd
merge develop
AfonsoMartins26 Jan 28, 2025
2ea657f
Merge branch 'develop' into am/profile
AfonsoMartins26 Mar 4, 2025
e806f1c
feat: merge main
AfonsoMartins26 Mar 4, 2025
1efa016
feat: merge develop
AfonsoMartins26 Mar 4, 2025
e78f8f4
feat: update image upload componente and banner working
AfonsoMartins26 Mar 6, 2025
0049f88
feat: change Uploaders
AfonsoMartins26 Mar 7, 2025
49d994a
feat: try to fix problem
AfonsoMartins26 Mar 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/atomic/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ defmodule Atomic.Accounts do
def update_user_picture(%User{} = user, attrs \\ %{}) do
user
|> User.picture_changeset(attrs)
|> IO.inspect()
|> Repo.update()
end

Expand All @@ -491,10 +492,11 @@ defmodule Atomic.Accounts do
{:error, %Ecto.Changeset{}}

"""
def update_user(%User{} = user, attrs \\ %{}, _after_save \\ &{:ok, &1}) do
def update_user(%User{} = user, attrs \\ %{}, after_save \\ &{:ok, &1}) do
user
|> User.changeset(attrs)
|> Repo.update()
|> after_save(after_save)
end

@doc """
Expand Down
5 changes: 4 additions & 1 deletion lib/atomic/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule Atomic.Accounts.User do
alias Atomic.Accounts.Course
alias Atomic.Activities.Enrollment
alias Atomic.Organizations.{Collaborator, Membership, Organization}
alias Atomic.Socials

@required_fields ~w(email password)a
@optional_fields ~w(name slug role confirmed_at phone_number course_id current_organization_id)a
Expand Down Expand Up @@ -39,6 +40,8 @@ defmodule Atomic.Accounts.User do
has_many :enrollments, Enrollment
has_many :collaborators, Collaborator

embeds_one :socials, Socials, on_replace: :update

many_to_many :organizations, Organization, join_through: Membership

timestamps()
Expand Down Expand Up @@ -70,7 +73,6 @@ defmodule Atomic.Accounts.User do

def picture_changeset(user, attrs) do
user
|> cast(attrs, @required_fields ++ @optional_fields)
|> cast_attachments(attrs, [:profile_picture])
end

Expand All @@ -83,6 +85,7 @@ defmodule Atomic.Accounts.User do
|> validate_email()
|> validate_slug()
|> validate_phone_number()
|> cast_embed(:socials, with: &Socials.changeset/2)
end

defp validate_email(changeset) do
Expand Down
5 changes: 3 additions & 2 deletions lib/atomic_web/components/sidebar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
alias Phoenix.LiveView.JS
import AtomicWeb.Components.Icon
alias Atomic.Organizations
import AtomicWeb.Components.Avatar

Check warning on line 8 in lib/atomic_web/components/sidebar.ex

View workflow job for this annotation

GitHub Actions / Code Quality (26.x, 1.14.x)

unused import AtomicWeb.Components.Avatar

Check warning on line 8 in lib/atomic_web/components/sidebar.ex

View workflow job for this annotation

GitHub Actions / OTP 26.x / Elixir 1.14.x

unused import AtomicWeb.Components.Avatar

attr :current_user, :map, required: true
attr :current_organization, :map, required: true
Expand Down Expand Up @@ -190,9 +191,9 @@

defp user_image(user) do
if user.profile_picture do
Uploaders.ProfilePicture.url({user, user.profile_picture}, :original)
Atomic.Uploaders.ProfilePicture.url({user, user.profile_picture}, :original)
else
nil
""
end
end

Expand Down
62 changes: 54 additions & 8 deletions lib/atomic_web/live/profile_live/form_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,55 @@ defmodule AtomicWeb.ProfileLive.FormComponent do
use AtomicWeb, :live_component

alias Atomic.Accounts

@extensions_whitelist ~w(.jpg .jpeg .gif .png)
alias AtomicWeb.Components.ImageUploader
import AtomicWeb.Components.Forms
import AtomicWeb.Components.{Button, Avatar}

@impl true
def mount(socket) do
{:ok,
socket
|> allow_upload(:picture, accept: @extensions_whitelist, max_entries: 1)}
def render(assigns) do
~H"""
<div class="px-4 pt-4">
<.form :let={f} for={@changeset} id="profile-form" phx-target={@myself} phx-change="validate" phx-submit="save">
<!-- Grid layout for profile picture, name, phone number, email, and social media fields -->
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<!-- Section for profile picture upload -->
<div class="flex flex-col items-center pr-4">
<%= if @user.profile_picture != NULL do %>
<%= label(f, :name, "Profile Picture", class: "mt-3 mb-1 text-sm font-medium text-gray-700") %>
<div class="mb-4 border-4">
<.avatar name={@user.name} color={:light_gray} class="h-36 w-36 rounded-full border-4 border-white text-4xl" type={:user} src={Uploaders.ProfilePicture.url({@user.profile_picture, @user}, :original)} />
</div>
<.live_component module={ImageUploader} id="uploader-profile-picture" uploads={@uploads} target={@myself} />
<% else %>
<%= label(f, :name, "Profile Picture", class: "mt-3 mb-1 text-sm font-medium text-gray-700") %>
<.live_component module={ImageUploader} id="uploader-profile-picture" uploads={@uploads} target={@myself} />
<% end %>
</div>
<div class="flex flex-col gap-6">
<!-- Name, phone number, email fields -->
<div class="grid grid-cols-1 gap-4">
<.field field={f[:name]} type="text" placeholder="Name" class="w-full" />
<.field field={f[:phone_number]} type="text" placeholder="Phone Number" class="w-full" />
<.field field={f[:email]} type="email" placeholder="Email" class="w-full" />
</div>
<!-- Social media fields positioned below name, phone, and email -->
<div class="grid w-full gap-x-4 gap-y-4 sm:grid-cols-1 md:grid-cols-4">
<.inputs_for :let={socials_form} field={f[:socials]}>
<.field field={socials_form[:instagram]} type="text" placeholder="Instagram" class="w-full" />
<.field field={socials_form[:facebook]} type="text" placeholder="Facebook" class="w-full" />
<.field field={socials_form[:x]} type="text" placeholder="X" class="w-full" />
<.field field={socials_form[:tiktok]} type="text" placeholder="TikTok" class="w-full" />
</.inputs_for>
</div>
</div>
</div>
<!-- Submit button -->
<div class="mt-8 flex w-full justify-end">
<.button size={:md} color={:white} icon="hero-cube">Save</.button>
</div>
</.form>
</div>
"""
end

@impl true
Expand All @@ -18,6 +59,10 @@ defmodule AtomicWeb.ProfileLive.FormComponent do

{:ok,
socket
|> allow_upload(:image,
accept: Uploaders.ProfilePicture.extension_whitelist(),
max_entries: 1
)
|> assign(assigns)
|> assign(:changeset, changeset)}
end
Expand All @@ -32,6 +77,7 @@ defmodule AtomicWeb.ProfileLive.FormComponent do
{:noreply, assign(socket, :changeset, changeset)}
end

@impl true
def handle_event("save", %{"user" => user_params}, socket) do
user = socket.assigns.user

Expand Down Expand Up @@ -69,8 +115,8 @@ defmodule AtomicWeb.ProfileLive.FormComponent do

defp consume_image_data(socket, user) do
consume_uploaded_entries(socket, :image, fn %{path: path}, entry ->
Accounts.update_user(user, %{
"image" => %Plug.Upload{
Accounts.update_user_picture(user, %{
"profile_picture" => %Plug.Upload{
content_type: entry.client_type,
filename: entry.client_name,
path: path
Expand Down
86 changes: 0 additions & 86 deletions lib/atomic_web/live/profile_live/form_component.html.heex

This file was deleted.

3 changes: 1 addition & 2 deletions lib/atomic_web/live/profile_live/show.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
defmodule AtomicWeb.ProfileLive.Show do
use AtomicWeb, :live_view

import AtomicWeb.Components.Button
import AtomicWeb.Components.Avatar
import AtomicWeb.Components.{Button, Avatar, Gradient}

alias Atomic.Accounts
alias Atomic.Organizations
Expand Down
81 changes: 70 additions & 11 deletions lib/atomic_web/live/profile_live/show.html.heex
Original file line number Diff line number Diff line change
@@ -1,14 +1,66 @@
<div>
<div class="pt-4 px-4">
<div class="flex items-center justify-between">
<div class="min-w-0 flex-1 space-y-2">
<div class="flex flex-row">
<h2 class="text-xl font-bold leading-7 text-zinc-900 sm:text-4xl">
<%= @user.name %>
</h2>
<div class="relative">
<div class="h-64 w-full border-b-2 bg-cover">
<.gradient class="h-64 w-full bg-cover bg-center" seed={@user.id} />
</div>
<!-- Profile Info Container -->
<div class="relative px-4 pt-4">
<div class="flex items-start">
<!-- Profile Picture -->
<div class="relative -mt-16 flex-shrink-0">
<div class="relative">
<.avatar name={@user.name} color={:light_gray} class="h-36 w-36 text-4xl rounded-full border-4 border-white" type={:user} src={Uploaders.ProfilePicture.url({@user.profile_picture, @user}, :original)} />
</div>
<p class="text-zinc-500">@<%= @user.slug %></p>
<div class="grid grid-cols-1 gap-4 py-6 mb-2 sm:grid-cols-2 lg:grid-cols-3">
</div>
<div class="flex-1 pl-6">
<!-- User Info -->
<h2 class="text-xl font-bold leading-7 text-zinc-900 sm:text-4xl">
<%= @user.name %>
</h2>

<div class="mt-2">
<%= if length(@organizations) > 0 do %>
<div class="mt-2">
<%= for organization <- @organizations do %>
<p class="text-lg font-semibold text-zinc-600 md:text-md lg:text-sm">
<%= organization.name %> - <%= Atomic.Organizations.get_role(@user.id, organization.id) %>
</p>
<% end %>
</div>
<% else %>
<p class="py-2">No organizations found.</p>
<% end %>
</div>
<!-- Social Media Links -->
<%= if @user.socials do %>
<div class="mt-4 flex gap-4">
<%= if @user.socials.tiktok do %>
<div class="flex flex-row items-center gap-x-1">
<img src="/images/tiktok.svg" class="h-5 w-5" alt="TikTok" />
<.link class="text-blue-500" target="_blank" href={"https://tiktok.com/" <> @user.socials.tiktok}>Tik Tok</.link>
</div>
<% end %>
<%= if @user.socials.instagram do %>
<div class="flex flex-row items-center gap-x-1">
<img src="/images/instagram.svg" class="h-5 w-5" alt="Instagram" />
<.link class="text-blue-500" target="_blank" href={"https://instagram.com/" <> @user.socials.instagram}>Instagram</.link>
</div>
<% end %>
<%= if @user.socials.facebook do %>
<div class="flex flex-row items-center gap-x-1">
<img src="/images/facebook.svg" class="h-5 w-5" alt="Facebook" />
<.link class="text-blue-500" target="_blank" href={"https://facebook.com/" <> @user.socials.facebook}>Facebook</.link>
</div>
<% end %>
<%= if @user.socials.x do %>
<div class="flex flex-row items-center gap-x-1">
<img src="/images/x.svg" class="h-5 w-5" alt="X" />
<.link class="text-blue-500" target="_blank" href={"https://x.com/" <> @user.socials.x}>X</.link>
</div>
<% end %>
</div>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is being used in multiple places please create a component

<% end %>

<div class="fllex-row mt-4 flex gap-8">
<%= if @user.email do %>
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">Email</dt>
Expand All @@ -31,8 +83,15 @@
</div>
<% end %>
</div>
<!-- Edit Button for Current User -->
<%= if @is_current_user do %>
<div class="mt-4 flex justify-start">
<.button patch={Routes.profile_edit_path(@socket, :edit, @user)}>

Check warning on line 89 in lib/atomic_web/live/profile_live/show.html.heex

View workflow job for this annotation

GitHub Actions / Code Quality (26.x, 1.14.x)

Routes.profile_edit_path/3 is undefined (module Routes is not available or is yet to be defined)

Check warning on line 89 in lib/atomic_web/live/profile_live/show.html.heex

View workflow job for this annotation

GitHub Actions / OTP 26.x / Elixir 1.14.x

Routes.profile_edit_path/3 is undefined (module Routes is not available or is yet to be defined)
<%= gettext("Edit") %>
</.button>
</div>
<% end %>
</div>
<.avatar class="sm:w-44 sm:h-44 sm:text-6xl" name={@user.name} size={:xl} color={:light_gray} />
</div>
<!-- Divider -->
<div class="py-6 mb-2 border-b border-zinc-200"></div>
Expand Down
Loading