diff --git a/assets/css/components/avatar.css b/assets/css/components/avatar.css index 455b343c4..0dc1c79e0 100644 --- a/assets/css/components/avatar.css +++ b/assets/css/components/avatar.css @@ -40,6 +40,10 @@ @apply size-24 text-4xl; } +.atomic-avatar--xxl { + @apply size-28 text-4xl; +} + /* Avatar - colors */ .atomic-avatar--primary { diff --git a/lib/atomic/organizations.ex b/lib/atomic/organizations.ex index e2c58e8ae..39afe7474 100644 --- a/lib/atomic/organizations.ex +++ b/lib/atomic/organizations.ex @@ -279,8 +279,8 @@ defmodule Atomic.Organizations do def list_memberships(%{"user_id" => user_id}, preloads) do Membership |> where([a], a.user_id == ^user_id) - |> Repo.preload(preloads) |> Repo.all() + |> Repo.preload(preloads) end @doc """ diff --git a/lib/atomic_web/components/avatar.ex b/lib/atomic_web/components/avatar.ex index 88e1ffb22..28c68e72b 100644 --- a/lib/atomic_web/components/avatar.ex +++ b/lib/atomic_web/components/avatar.ex @@ -16,7 +16,7 @@ defmodule AtomicWeb.Components.Avatar do doc: "The type of entity associated with the avatar." attr :size, :atom, - values: [:xs, :sm, :md, :lg, :xl], + values: [:xs, :sm, :md, :lg, :xl, :xxl], default: :md, doc: "The size of the avatar." @@ -69,7 +69,7 @@ defmodule AtomicWeb.Components.Avatar do doc: "The type of entity associated with the avatars." attr :size, :atom, - values: [:xs, :sm, :md, :lg, :xl], + values: [:xs, :sm, :md, :lg, :xl, :xxl], default: :md, doc: "The size of the avatars." diff --git a/lib/atomic_web/components/socials.ex b/lib/atomic_web/components/socials.ex index 07f4a1281..a0e8e4fb6 100644 --- a/lib/atomic_web/components/socials.ex +++ b/lib/atomic_web/components/socials.ex @@ -9,13 +9,13 @@ defmodule AtomicWeb.Components.Socials do assigns = assign(assigns, :socials_with_values, get_social_values(assigns.entity)) ~H""" -
+
<%= for {social, icon, url_base, social_value} <- assigns.socials_with_values do %> <%= if social_value do %>
icon} class="h-5 w-5" alt={Atom.to_string(social)} /> <.link class="capitalize text-blue-500" target="_blank" href={url_base <> social_value}> - {Atom.to_string(social)} + {social_value}
<% end %> diff --git a/lib/atomic_web/live/profile_live/show.ex b/lib/atomic_web/live/profile_live/show.ex index 3ea935d48..aedd7fe46 100644 --- a/lib/atomic_web/live/profile_live/show.ex +++ b/lib/atomic_web/live/profile_live/show.ex @@ -1,9 +1,10 @@ defmodule AtomicWeb.ProfileLive.Show do use AtomicWeb, :live_view - import AtomicWeb.Components.{Button, Avatar, Gradient, Socials} + import AtomicWeb.Components.{Button, Tabs, Avatar, Gradient, Socials} import AtomicWeb.Components.ImageUploader import AtomicWeb.LiveHelpers + alias AtomicWeb.HomeLive.Components.FollowSuggestions.Suggestion alias Atomic.Accounts alias Atomic.Organizations @@ -27,7 +28,7 @@ defmodule AtomicWeb.ProfileLive.Show do end @impl true - def handle_params(%{"slug" => user_slug}, _, socket) do + def handle_params(%{"slug" => user_slug} = params, _, socket) do user = Accounts.get_user_by_slug(user_slug) is_current_user = @@ -35,6 +36,8 @@ defmodule AtomicWeb.ProfileLive.Show do organizations = Organizations.list_user_organizations(user.id) + memberships = Organizations.list_memberships(%{"user_id" => user.id}, [:organization]) + {:noreply, socket |> assign(:page_title, user.name) @@ -42,6 +45,70 @@ defmodule AtomicWeb.ProfileLive.Show do |> assign(:current_page, :profile) |> assign(:user, user) |> assign(:organizations, organizations) - |> assign(:is_current_user, is_current_user)} + |> assign(:memberships, memberships) + |> assign(:is_current_user, is_current_user) + |> assign(:current_tab, current_tab(socket, params))} + end + + @impl true + def handle_event("unfollow", %{"organization_id" => organization_id}, socket) do + membership = + Organizations.get_membership_by_user_id_and_organization_id!( + socket.assigns.current_user.id, + organization_id + ) + + organization = Organizations.get_organization!(organization_id) + + case Organizations.delete_membership(membership) do + {:ok, _organization} -> + # Reloads memberships list after unfollowing a new one + memberships = + Organizations.list_memberships(%{"user_id" => socket.assigns.user.id}, [:organization]) + + {:noreply, + socket + |> assign(:memberships, memberships || []) + |> put_flash(:success, "Unfollowed " <> organization.name)} + + {:error, _changeset} -> + {:noreply, + socket + |> put_flash(:error, "Failed to unfollow " <> organization.name)} + end + end + + @impl true + def handle_event("follow", %{"organization_id" => organization_id}, socket) do + attrs = %{ + role: :follower, + user_id: socket.assigns.current_user.id, + created_by_id: socket.assigns.current_user.id, + organization_id: organization_id + } + + organization = Organizations.get_organization!(organization_id) + + case Organizations.create_membership(attrs) do + {:ok, _organization} -> + # Reloads memberships list after following a new one + memberships = + Organizations.list_memberships(%{"user_id" => socket.assigns.user.id}, [:organization]) + + {:noreply, + socket + |> assign(:memberships, memberships || []) + |> put_flash(:success, "Started following " <> organization.name) + |> push_patch(to: ~p"/profile/#{socket.assigns.user.slug}")} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign(socket, :changeset, changeset)} + end + end + + defp current_tab(_socket, params) when is_map_key(params, "tab") do + params["tab"] end + + defp current_tab(_socket, _params), do: "following" end diff --git a/lib/atomic_web/live/profile_live/show.html.heex b/lib/atomic_web/live/profile_live/show.html.heex index a41eef204..47eaef948 100644 --- a/lib/atomic_web/live/profile_live/show.html.heex +++ b/lib/atomic_web/live/profile_live/show.html.heex @@ -1,5 +1,7 @@
-
+
+ +
<%= if @user.banner do %> <.image_uploader editable={false} id="banner-picture" class="h-64 w-full" image_class="h-[290px] w-full object-cover" upload={@uploads.banner} icon="hero-photo" memory_unit="MB" image={Uploaders.Banner.url({@user.banner, @user}, :original, signed: true)} /> @@ -7,76 +9,142 @@ <.gradient class="h-64 w-full bg-center object-cover" seed={@user.id} /> <% end %>
-
-
-
-
- <%= if @user.profile_picture do %> - <.image_uploader editable={false} id="profile-picture" class="aspect-square w-36 border-4 border-white" rounded upload={@uploads.profile_picture} icon="hero-user" memory_unit="GB" image={Uploaders.ProfilePicture.url({@user.profile_picture, @user}, :original, signed: true)}> - <:placeholder> - <.avatar size={:xl} name={@user.name} type={:user} /> - - - <% else %> - <.avatar size={:xl} name={@user.name} type={:user} /> - <% end %> -
+ + +
+
+
+ <%= if @user.profile_picture do %> + <.image_uploader editable={false} id="profile-picture" class="aspect-square w-36 border-4 border-white" rounded upload={@uploads.profile_picture} icon="hero-user" memory_unit="GB" image={Uploaders.ProfilePicture.url({@user.profile_picture, @user}, :original, signed: true)}> + <:placeholder> + <.avatar size={:xxl} name={@user.name} type={:user} /> + + + <% else %> + <.avatar size={:xxl} name={@user.name} type={:user} /> + <% end %>
-
-

- {@user.name} -

-
- <%= if length(@organizations) > 0 do %> -
- <%= for organization <- @organizations do %> -

- {organization.name} - {Atomic.Organizations.get_role(@user.id, organization.id)} -

- <% end %> -
- <% else %> -

{gettext("No organizations found.")}

- <% end %> -
- <%= if @user.socials do %> -
- <.socials entity={@user} /> +
+
+ + +
+
+ + +

+ {@user.name} +

+ + +
+ <%= if length(@organizations) > 0 do %> +
+ <%= for organization <- @organizations do %> +

+ {organization.name} - {Atomic.Organizations.get_role(@user.id, organization.id)} +

+ <% end %>
+ <% else %> +

{gettext("No organizations found.")}

<% end %> -
- <%= if @user.email do %> -
-
{gettext("Email")}
-
- - {@user.email} - -
-
- <% end %> - <%= if @user.phone_number do %> -
-
{gettext("Phone")}
-
- - {@user.phone_number} - -
-
- <% end %> -
+ + + <%= if @user.socials do %> +
+ <.socials entity={@user} /> +
+ <% end %> +
+ + +
+ <%= if @is_current_user do %> + <.button patch={~p"/profile/#{@user}/edit"} icon="hero-pencil-square" size={:md} full_width={true}> + {gettext("Edit Profile")} + + <% end %>
-
- <%= if @is_current_user do %> -
- <.button patch={~p"/profile/#{@user}/edit"}> - {gettext("Edit")} - + + + +
+
+
+ <.tabs class="px-4 sm:px-6 lg:px-8"> + <.link patch="?tab=following" replace={false}> + <.tab active={@current_tab == "following"}> + {gettext("Following")} + + + <.link patch="?tab=activity" replace={false}> + <.tab active={@current_tab == "activity"}> + {gettext("Activity")} + + + <.link patch="?tab=about" replace={false}> + <.tab active={@current_tab == "about"}> + {gettext("About")} + + +
- <% end %> +
+ + <%= case @current_tab do %> + <% "following" -> %> +
+
+

{gettext("Following Organizations:")}

+ +
+
+ <%= if Enum.any?(@memberships) do %> + <%= for membership <- @memberships do %> + <% org = membership.organization %> + + <% is_viewer_following = + if @current_user do + Organizations.get_role(@current_user.id, org.id) == :follower + else + false + end %> + +
+ <.live_component id={org.id} module={Suggestion} organization={org} current_user={@current_user} is_following={is_viewer_following} /> +
+ <% end %> + + +
+
+

{gettext("Browse more organizations")}

+
+ <.button patch={~p"/organizations"} color={:white} size={:xs}> + {gettext("Browse Organizations")} + +
+ +
+ <% else %> +

No memberships found...

+ <% end %> +
+
+
+
+ <% "activity" -> %> +
+ activity +
+ <% "about" -> %> +
+ about +
+ <% end %>
diff --git a/lib/atomic_web/templates/layout/live.html.heex b/lib/atomic_web/templates/layout/live.html.heex index 461ff37d1..2a5dac7cc 100644 --- a/lib/atomic_web/templates/layout/live.html.heex +++ b/lib/atomic_web/templates/layout/live.html.heex @@ -12,7 +12,7 @@ {render("_live_navbar.html", assigns)}
-
+
{@inner_content}