Let's Build An Instagram Clone With The PETAL(Phoenix, Elixir, TailwindCSS, AlpineJS, LiveView) Stack [PART 3]
Tags: elixir, webdev, programming, tutorial
Published: 2021-04-28 09:48:28

In [part 2](https://elixirprogrammer.com/learn/lets-build-an-instagram-clone-with-the-petal-phoenix-elixir-tailwindcss-alpinejs-liveview-stack-part-2) we added the ability to edit accounts and upload user's avatars, in this part, we will work on user's profiles. You can catch up with the [Instagram Clone GitHub Repo](https://github.com/elixirprogrammer/InstagramClonePETAL).
&nbsp;
### User Profiles

First, we need the route, open `lib/instagram_clone_web/router.ex` add the following route under root `:browser` scope:

```elixir
scope "/", InstagramCloneWeb do
  pipe_through :browser
  
  live "/", PageLive,  :index
  live "/:username", UserLive.Profile # THIS LINE WAS ADDED
end
```

Then let's create our liveview files inside `lib/instagram_clone_web/live/user_live` folder:

`lib/instagram_clone_web/live/user_live/profile.ex`
`lib/instagram_clone_web/live/user_live/profile.html.leex`

Add the following inside `lib/instagram_clone_web/live/user_live/profile.ex`:

```elixir
defmodule InstagramCloneWeb.UserLive.Profile do
  use InstagramCloneWeb, :live_view
  
  @impl true
  def mount(%{"username" => username}, session, socket) do
    socket = assign_defaults(session, socket)

    {:ok,
      socket
      |> assign(username: username)}
  end

end
```

Inside `lib/instagram_clone_web/live/user_live/profile.html.leex`:
```html
<h1 class="text-5xl"><%= @username %></h1>
```

Open our navigation header `lib/instagram_clone_web/live/header_nav_component.html.leex` on line 56 let's add our new route:

```elixir
<%= live_patch to: Routes.live_path(@socket, InstagramCloneWeb.UserLive.Profile, @current_user.username)  do  %>
```

We need to find the user with the username param passed to our Liveview, open `lib/instagram_clone/accounts.ex` add a `profile()` function that will do that for us:

```elixir
...

  @doc """
  Gets the user with the given username param.
  """
  def profile(param) do
    Repo.get_by!(User, username: param)
  end

...
```

Let's update our mount function inside `lib/instagram_clone_web/live/user_live/profile.ex`:

```elixir
defmodule InstagramCloneWeb.UserLive.Profile do
  use InstagramCloneWeb, :live_view
  
  @impl true
  def mount(_params, session, socket) do
    socket = assign_defaults(session, socket)

    {:ok, socket}
  end

end
```

We need to handle the username param, open `lib/instagram_clone_web.ex` and update our `live_view()` macro to the following:
```elixir
  def live_view do
    quote do
      use Phoenix.LiveView,
        layout: {InstagramCloneWeb.LayoutView, "live.html"}

      unquote(view_helpers())
      import InstagramCloneWeb.LiveHelpers

      alias InstagramClone.Accounts.User
      alias InstagramClone.Accounts # <-- THIS LINE WAS ADDED
      @impl true
      def handle_info(%{event: "logout_user", payload: %{user: %User{id: id}}}, socket) do
        with %User{id: ^id} <- socket.assigns.current_user do
          {:noreply,
            socket
            |> redirect(to: "/")
            |> put_flash(:info, "Logged out successfully.")}
        else
          _any -> {:noreply, socket}
        end
      end
	  @doc """
	    Because we are calling this function in each liveview, 
	    and we needed access to the username param in our profile liveview, 
	    we updated this function for when the username param is present,
	    get the user and assign it along with page title to the socket
	  """
      @impl true
      def handle_params(params, uri, socket) do
        if Map.has_key?(params, "username") do
          %{"username" => username} = params
          user = Accounts.profile(username)
          {:noreply,
          socket
          |> assign(current_uri_path: URI.parse(uri).path)
          |> assign(user: user, page_title: "#{user.full_name} (@#{user.username})")}
        else
          {:noreply,
            socket
            |> assign(current_uri_path: URI.parse(uri).path)}
        end
      end
    end
  end
```

#### Repo.get_by!(queryable, clauses, opts)
Fetches a single result from the query. Raises [`Ecto.NoResultsError`](https://hexdocs.pm/ecto/Ecto.NoResultsError.html) if no record was found, or more than one entry. **In production 404 error when no record found.**

Open `lib/instagram_clone_web/live/header_nav_component.htm.leex` on line 57 add the following to the profile list tag to close the dropdown menu when selected:
```html
<li  @click="open = false"  class="py-2 px-4 hover:bg-gray-50">Profile</li>
```

Inside `lib/instagram_clone_web/live/user_live/profile.html.leex`:

```html
<header class="flex justify-center px-10">
  <!-- Profile Picture Section -->
  <section class="w-1/4">
      <%= img_tag @user.avatar_url,
          class: "w-40 h-40 rounded-full object-cover object-center" %>
  </section>
  <!-- END Profile Picture Section -->

  <!-- Profile Details Section -->
  <section class="w-3/4">
    <div class="flex px-3 pt-3">
        <h1 class="truncate md:overflow-clip text-2xl md:text-2xl text-gray-500 mb-3"><%= @user.username %></h1>
        <span class="ml-11"><button class="py-1 px-5 border-none shadow rounded text-gray-50 hover:bg-light-blue-600 bg-light-blue-500">Follow</button></span>
    </div>

    <div>
      <ul class="flex p-3">
          <li><b>0</b> Posts</li>
          <li class="ml-11"><b>0</b> Followers</li>
          <li class="ml-11"><b>0</b> Following</li>
      </ul>
    </div>

    <div class="p-3">
      <h2 class="text-md text-gray-600 font-bold"><%= @user.full_name %></h2>
      <%= if @user.bio do %>
        <p class="max-w-full break-words"><%= @user.bio %></p>
      <% end %>
      <%= if @user.website do %>
        <%= link display_website_uri(@user.website),
          to: @user.website,
          target: "_blank", rel: "noreferrer",
          class: "text-blue-700" %>
      <% end %>
    </div>
  </section>
  <!-- END Profile Details Section -->
</header>

<section class="border-t-2 mt-5">
  <ul class="flex justify-center text-center space-x-20">
    <li class="pt-4 px-1 text-sm text-gray-600 border-t-2 border-black -mt-0.5">
       POSTS
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      IGTV
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      SAVED
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      TAGGED
    </li>
  </ul>
</section>
```

Open `lib/instagram_clone_web/live/render_helpers.ex` add the following 2 functions to display and get the website uri:

```elixir
  def display_website_uri(website) do
    website = website
    |> String.replace_leading("https://", "")
    |> String.replace_leading("http://", "")
    website
  end
```

Now we need to create our user follow component inside `lib/instagram_clone_web/live/user_live`:

`lib/instagram_clone_web/live/user_live/follow_component.ex`

```elixir
defmodule InstagramCloneWeb.UserLive.FollowComponent do
  use InstagramCloneWeb, :live_component

  def render(assigns) do
    ~L"""
    <button
      phx-target="<%= @myself %>"
      phx-click="toggle-status"
      class="<%= @follow_btn_styles? %>"><%= @follow_btn_name? %></button>
    """
  end

  def handle_event("toggle-status", _params, socket) do
    follow_btn_name? = get_follow_btn_name?(socket.assigns.follow_btn_name?)
    follow_btn_styles? = get_follow_btn_styles?(socket.assigns.follow_btn_name?)

    :timer.sleep(200)
    {:noreply,
      socket
      |> assign(follow_btn_name?: follow_btn_name?)
      |> assign(follow_btn_styles?: follow_btn_styles?)}
  end

  defp get_follow_btn_name?(name) when name == "Follow" do
    "Unfollow"
  end
  defp get_follow_btn_name?(name) when name == "Unfollow" do
    "Follow"
  end

  defp get_follow_btn_styles?(name) when name == "Follow"  do
    "py-1 px-2 text-red-500 border-2 rounded font-semibold hover:bg-gray-50 focus:outline-none"
  end
  defp get_follow_btn_styles?(name) when name == "Unfollow" do
    "py-1 px-5 border-none shadow rounded text-gray-50 hover:bg-light-blue-600 bg-light-blue-500 focus:outline-none"
  end
end
```

In our render function, we just have the button, with a click function that's going to get handle inside the component. It has 2 assigns, one for the name of the button and the other one for the styles, those are going to get set in our profile LiveView, then in our event function, we are assigning them back to the socket.

Now let's use our component in our profile template, open `lib/instagram_clone_web/live/user_live/profile.html.leex` and update the file to the following:

```html
<header class="flex justify-center px-10">
  <!-- Profile Picture Section -->
  <section class="w-1/4">
      <%= img_tag @user.avatar_url,
          class: "w-40 h-40 rounded-full object-cover object-center" %>
  </section>
  <!-- END Profile Picture Section -->

  <!-- Profile Details Section -->
  <section class="w-3/4">
    <div class="flex px-3 pt-3">
        <h1 class="truncate md:overflow-clip text-2xl md:text-2xl text-gray-500 mb-3"><%= @user.username %></h1>
        <span class="ml-11">
	      <!-- THE BUTTON WAS REPLACED FOR THE COMPONENT -->
          <%= cond do %>
            <% @current_user && @current_user == @user -> %>
              <%= live_patch "Edit Profile",
                to: Routes.live_path(@socket, InstagramCloneWeb.UserLive.Settings),
                class: "py-1 px-2 border-2 rounded font-semibold hover:bg-gray-50" %>

            <% @current_user -> %>
              <%= live_component @socket,
                InstagramCloneWeb.UserLive.FollowComponent,
                id: @user.id,
                follow_btn_name?: @follow_btn_name?,
                follow_btn_styles?: @follow_btn_styles? %>

            <% true -> %>
              <%= link "Follow", to: Routes.user_session_path(@socket, :new), class: "py-1 px-5 border-none shadow rounded text-gray-50 hover:bg-light-blue-600 bg-light-blue-500" %>
          <% end %>
          <!-- ALL THIS UNTIL HERE WAS ADDED -->
        </span>
    </div>

    <div>
      <ul class="flex p-3">
          <li><b>0</b> Posts</li>
          <li class="ml-11"><b>0</b> Followers</li>
          <li class="ml-11"><b>0</b> Following</li>
      </ul>
    </div>

    <div class="p-3">
      <h2 class="text-md text-gray-600 font-bold"><%= @user.full_name %></h2>
      <%= if @user.bio do %>
        <p class="max-w-full break-words"><%= @user.bio %></p>
      <% end %>
      <%= if @user.website do %>
        <%= link display_website_uri(@user.website),
          to: @user.website,
          target: "_blank", rel: "noreferrer",
          class: "text-blue-700" %>
      <% end %>
    </div>
  </section>
  <!-- END Profile Details Section -->
</header>

<section class="border-t-2 mt-5">
  <ul class="flex justify-center text-center space-x-20">
    <li class="pt-4 px-1 text-sm text-gray-600 border-t-2 border-black -mt-0.5">
       POSTS
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      IGTV
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      SAVED
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      TAGGED
    </li>
  </ul>
</section>
```

We created a conditional to display the right button to users, when logged in and the user is on his profile it will get an edit profile link, when logged in and any other profile, we display the component, when not logged in, just a link to the login page. Now we need the assigns that we are sending to the component, just when the component is being displayed,  open `lib/instagram_clone_web/live/user_live/profile.ex` and update the file to the following:

```elixir
defmodule InstagramCloneWeb.UserLive.Profile do
  use InstagramCloneWeb, :live_view

  alias InstagramClone.Accounts

  @impl true
  def mount(%{"username" => username}, session, socket) do
    socket = assign_defaults(session, socket)
    current_user = socket.assigns.current_user
    user = Accounts.profile(username)

    get_assigns(socket, current_user, user)
  end

  defp get_follow_btn_name? do
    "Follow"
  end

  defp get_follow_btn_styles? do
    "py-1 px-5 border-none shadow rounded text-gray-50 hover:bg-light-blue-600 bg-light-blue-500 focus:outline-none"
  end

  defp get_assigns(socket, current_user, user) do
    if current_user && current_user !== user do
      follow_btn_name? = get_follow_btn_name?()
      follow_btn_styles? = get_follow_btn_styles?()

      {:ok,
        socket
        |> assign(follow_btn_name?: follow_btn_name?)
        |> assign(follow_btn_styles?: follow_btn_styles?)}
    else
      {:ok, socket}
    end
  end

end
```

Let's create a Follow schema to handle followers in our terminal:
 
`$ mix phx.gen.schema Accounts.Follows accounts_follows follower_id:references:users followed_id:references:users` 

Open the migration that was generated and add the following:

```elixir
defmodule  InstagramClone.Repo.Migrations.CreateAccountsFollows  do
  use Ecto.Migration
  
  def  change  do
    create table(:accounts_follows)  do
      add :follower_id,  references(:users,  on_delete:  :delete_all)
      add :followed_id,  references(:users,  on_delete:  :delete_all)

      timestamps()
    end
	
    create index(:accounts_follows,  [:follower_id])
    create index(:accounts_follows,  [:followed_id])
  end
end
```

Also let's add 2 new fields to users table to keep track of total followers and followings, back in our terminal:

`$ mix ecto.gen.migration adds_follower_followings_count_to_users_table`

Open the migration generated and add the following:

```elixir
defmodule InstagramClone.Repo.Migrations.AddsFollowerFollowingsCountToUsersTable do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :followers_count, :integer, default: 0
      add :following_count, :integer, default: 0
    end
  end
end
```

Back to the terminal run `$ mix ecto.migrate`

Now open the new schema that was generated under `lib/instagram_clone/accounts/follows.ex` inside that file add the following:

```elixir
defmodule InstagramClone.Accounts.Follows do
  use Ecto.Schema

  alias InstagramClone.Accounts.User

  schema "accounts_follows" do
    belongs_to :follower, User
    belongs_to :followed, User

    timestamps()
  end

end
```

Inside `lib/instagram_clone/accounts/user.ex` add the following:
```elixir
  alias InstagramClone.Accounts.Follows

  @derive {Inspect,  except:  [:password]}
  schema "users"  do
    field :email,  :string
    field :password,  :string,  virtual:  true
    field :hashed_password,  :string
    field :confirmed_at,  :naive_datetime
    field :username,  :string
    field :full_name,  :string
    field :avatar_url,  :string,  default:  "/images/default-avatar.png"
    field :bio,  :string
    field :website,  :string
    field :followers_count, :integer, default: 0
    field :following_count, :integer, default: 0
    has_many :following, Follows,  foreign_key:  :follower_id
    has_many :followers, Follows,  foreign_key:  :followed_id
    timestamps()
  end

  def registration_changeset(user, attrs, opts \\ []) do
    user
    |> cast(attrs, [:email, :password, :username, :full_name, :avatar_url, :bio, :website])
    |> validate_required([:username, :full_name])
    |> validate_length(:username, min: 5, max: 30)
    |> validate_format(:username, ~r/^[a-zA-Z0-9_.-]*$/, message: "Please use letters and numbers without space(only characters allowed _ . -)")
    |> unique_constraint(:username)
    |> unsafe_validate_unique(:username, InstagramClone.Repo)
    |> validate_length(:full_name, min: 4, max: 255)
    |> validate_format(:full_name,  ~r/^[a-zA-Z0-9 ]*$/,  message:  "Please use letters and numbers")
    |> validate_website_schemes()
    |> validate_website_authority()
    |> validate_email()
    |> validate_password(opts)
  end

  defp validate_website_schemes(changeset) do
    validate_change(changeset, :website, fn :website, website ->
      uri = URI.parse(website)
      if uri.scheme, do: check_uri_scheme(uri.scheme), else: [website: "Enter a valid website"]
    end)
  end

  defp validate_website_authority(changeset) do
    validate_change(changeset, :website, fn :website, website ->
      authority = URI.parse(website).authority
      if String.match?(authority, ~r/^[a-zA-Z0-9.-]*$/) do
        []
      else
        [website: "Enter a valid website"]
      end
    end)
  end

  defp check_uri_scheme(scheme) when scheme == "http", do: []
  defp check_uri_scheme(scheme) when scheme == "https", do: []
  defp check_uri_scheme(_scheme), do: [website: "Enter a valid website"]
```

Inside `lib/instagram_clone/accounts.ex` add the following functions at the bottom of your file, and `alias InstagramClone.Accounts.Follows` at the top:

```elixir
  @doc """
  Creates a follow to the given followed user, and builds
  user association to be able to preload the user when associations are loaded,
  gets users to update counts, then performs 3 Repo operations,
  creates the follow, updates user followings count, and user followers count,
  we select the user in our updated followers count query, that gets returned
  """
  def create_follow(follower, followed, user) do
    follower = Ecto.build_assoc(follower, :following)
    follow = Ecto.build_assoc(followed, :followers, follower)
    update_following_count = from(u in User, where: u.id == ^user.id)
    update_followers_count = from(u in User, where: u.id == ^followed.id, select: u)

    Ecto.Multi.new()
    |> Ecto.Multi.insert(:follow, follow)
    |> Ecto.Multi.update_all(:update_following, update_following_count, inc: [following_count: 1])
    |> Ecto.Multi.update_all(:update_followers, update_followers_count, inc: [followers_count: 1])
    |> Repo.transaction()
    |> case do
      {:ok,   %{update_followers: update_followers}} ->
        {1, user} = update_followers
        hd(user)
    end
  end

  @doc """
  Deletes following association with given user,
  then performs 3 Repo operations, to delete the association,
  update followings count, update and select followers count,
  updated followers count gets returned
  """
  def unfollow(follower_id, followed_id) do
    follow = following?(follower_id, followed_id)
    update_following_count = from(u in User, where: u.id == ^follower_id)
    update_followers_count = from(u in User, where: u.id == ^followed_id, select: u)

    Ecto.Multi.new()
    |> Ecto.Multi.delete(:follow, follow)
    |> Ecto.Multi.update_all(:update_following, update_following_count, inc: [following_count: -1])
    |> Ecto.Multi.update_all(:update_followers, update_followers_count, inc: [followers_count: -1])
    |> Repo.transaction()
    |> case do
      {:ok,   %{update_followers: update_followers}} ->
        {1, user} = update_followers
        hd(user)
    end
  end

  @doc """
  Returns nil if not found
  """
  def following?(follower_id, followed_id) do
    Repo.get_by(Follows, [follower_id: follower_id, followed_id: followed_id])
  end

  @doc """
  Returns all user's followings
  """
  def list_following(user) do
    user = user |> Repo.preload(:following)
    user.following |> Repo.preload(:followed)
  end

  @doc """
  Returns all user's followers
  """
  def list_followers(user) do
    user = user |> Repo.preload(:followers)
    user.followers |> Repo.preload(:follower)
  end


```

Now let's update our file `lib/instagram_clone_web/live/user_live/profile.ex`:

```elixir
defmodule InstagramCloneWeb.UserLive.Profile do
  use InstagramCloneWeb, :live_view

  alias InstagramClone.Accounts
  alias InstagramCloneWeb.UserLive.FollowComponent

  @impl true
  def mount(_params, session, socket) do
    socket = assign_defaults(session, socket)

    {:ok, socket}
  end

  @impl true
  def handle_info({FollowComponent, :update_totals, updated_user}, socket) do
    {:noreply, socket |> assign(user: updated_user)}
  end

end
```

We are going to set the follow button inside the component and just send a message to parent liveview to update the count.

Inside `lib/instagram_clone_web/live/user_live/profile.html.leex` update the assings names on line 20, and 27:
```elixir
<%  @current_user ->  %>
  <%= live_component @socket,
    InstagramCloneWeb.UserLive.FollowComponent,
    id:  @user.id,
    user:  @user,
    current_user:  @current_user %>

<%  true  ->  %>
  <%= link "Follow",  to: Routes.user_session_path(@socket,  :new),  class:  "user-profile-follow-btn"  %>
  
```

Open `assets/css/app.scss` and add the following:
```css
/* This file is for your main application css. */
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@import "../node_modules/nprogress/nprogress.css";

@layer components {
  .user-profile-unfollow-btn {
    @apply  py-1  px-2  text-red-500  border-2  rounded  font-semibold  hover:bg-gray-50
  }

  .user-profile-follow-btn {
    @apply  py-1  px-5  border-none  shadow  rounded  text-gray-50  hover:bg-light-blue-600  bg-light-blue-500
  }
}

/* Styles for handling buttons click events */
.while-submitting  {  display: none;  }

.phx-click-loading  {
  .while-submitting  {  display: inline;  }
  .btns  {  display: none;  }
}
```

Open `lib/instagram_clone_web/live/user_live/follow_component.ex` and update the file to the following:

```elixir
defmodule InstagramCloneWeb.UserLive.FollowComponent do
  use InstagramCloneWeb, :live_component

  alias InstagramClone.Accounts

  @impl true
  def update(assigns, socket) do
    get_btn_status(socket, assigns)
  end

  @impl true
  def render(assigns) do
    ~L"""
    <button
      phx-target="<%= @myself %>"
      phx-click="toggle-status"
      class="focus:outline-none">

      <span class="while-submitting">
        <span class="<%= @follow_btn_styles %> inline-flex items-center transition ease-in-out duration-150 cursor-not-allowed">
          <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
            <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
            <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
          </svg>
          Saving
        </span>
      </span>

      <span class="<%= @follow_btn_styles %>"><%= @follow_btn_name %><span>
    </button>
    """
  end

  @impl true
  def handle_event("toggle-status", _params, socket) do
    current_user = socket.assigns.current_user
    user = socket.assigns.user

    :timer.sleep(300)
    if Accounts.following?(current_user.id, user.id) do
      unfollow(socket, current_user.id, user.id)
    else
      follow(socket, current_user, user)
    end
  end

  defp get_btn_status(socket, assigns) do
    if Accounts.following?(assigns.current_user.id, assigns.user.id) do
      get_socket_assigns(socket, assigns, "Unfollow", "user-profile-unfollow-btn")
    else
      get_socket_assigns(socket, assigns, "Follow", "user-profile-follow-btn")
    end
  end

  defp get_socket_assigns(socket, assigns, btn_name, btn_styles) do
    {:ok,
      socket
      |> assign(assigns)
      |> assign(follow_btn_name: btn_name)
      |> assign(follow_btn_styles: btn_styles)}
  end

  defp follow(socket, current_user, user) do
    updated_user = Accounts.create_follow(current_user, user, current_user)
    # Message sent to the parent liveview to update totals
    send(self(), {__MODULE__, :update_totals, updated_user})
    {:noreply,
      socket
      |> assign(follow_btn_name: "Unfollow")
      |> assign(follow_btn_styles: "user-profile-unfollow-btn")}
  end

  defp unfollow(socket, current_user_id, user_id) do
    updated_user = Accounts.unfollow(current_user_id, user_id)
    # Message sent to the parent liveview to update totals
    send(self(), {__MODULE__, :update_totals, updated_user})
    {:noreply,
      socket
      |> assign(follow_btn_name: "Follow")
      |> assign(follow_btn_styles: "user-profile-follow-btn")}
  end

end
```

Inside `lib/instagram_clone_web/live/user_live/profile.html.leex` update lines 37, 38:

```html
<li class="ml-11"><b><%=  @user.followers_count %></b> Followers</li>
<li class="ml-11"><b><%=  @user.following_count %></b> Following</li>
``` 

Everything should work fine, but there is a problem that was introduced, you can see it in the gif image down below.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rleq1r9g5hodsdujhyv6.gif)

When the follow button gets triggered and we navigate to our profile, we are still on the same route, we don't get the right edit profile button, because we are using `live_patch/2` in our header navigation, so when we go to our profile, there's no change, the only thing changing is the `@user` so in our template the case that we are using to display the right button never gets called.

- `link/2`  and  `redirect/2`  do full page reloads
    
- `live_redirect/2`  and  `push_redirect/2`  reloads the LiveView but keeps the current layout
    
- `live_patch/2`  and  `push_patch/2`  updates the current LiveView and sends only the minimal diff

The "patch" operations must be used when you want to navigate to the current LiveView, simply updating the URL and the current parameters, without mounting a new LiveView. When patch is used, the handle_params/3 callback is invoked and the minimal set of changes are sent to the client. See the next section for more information.

An easy rule of thumb is to stick with live_redirect/2 and push_redirect/2 and use the patch helpers only in the cases where you want to minimize the amount of data sent when navigating within the same LiveView (for example, if you want to change the sorting of a table while also updating the URL).

The only solution that we can think of that can solve that problem is using `live_redirect/2` in our header navigation to reload the LiveView. Open `lib/instagram_clone_web/live/header_nav_component.html.leex` on line 56 do the following:

```elixir
<%= live_redirect to: Routes.live_path(@socket, InstagramCloneWeb.UserLive.Profile,  @current_user.username)  do  %>
```

Now the LiveView reloads and we get the right button displayed. I also noticed why we needed to use the `handle_params()` function to assign the user, because of the conflict that when we use `live_patch/2` nothing was changing and the user assign was not getting reload, but now the LiveView reloads so we can set the user in our LiveView `mount()` function.

Open `lib/instagram_clone_web/live/user_live/profile.ex` and update the `mount()` function to the following:

```elixir
  @impl true
  def mount(%{"username" => username}, session, socket) do
    socket = assign_defaults(session, socket)
    user = Accounts.profile(username)

    {:ok,
      socket
      |> assign(user: user)
      |> assign(page_title: "#{user.full_name} (@#{user.username})")}
  end
```

Open `lib/instagram_clone_web.ex` and inside our `live_view()` macro update the `handle_params()` function to the following:

```elixir
      @impl true
      def handle_params(_params, uri, socket) do
        {:noreply,
          socket
          |> assign(current_uri_path: URI.parse(uri).path)}
      end
```

Also we no longer need to close the dropdown menu when selected, when we are on the profile page, so open `lib/instagram_clone_web/live/header_nav_component.htm.leex` on line 57, delete the AlpineJS directive:

```html
<li class="py-2 px-4 hover:bg-gray-50">Profile</li>
```

Let's stick to only use `live_patch/2` when we just want to display something that gets updated with URLS params, that doesn't trigger any action or changes any state.

I also decided to handle the conditional for the follow button inside the LiveView, it's personal preference, you can choose either way or how you feel comfortable. Open `lib/instagram_clone_web/live/user_live/profile.ex` and add the following private function:

```elixir
defp get_action(user, current_user) do
  cond do
    current_user && current_user == user -> :edit_profile
    current_user -> :follow_component
    true -> :login_btn
  end
end
```

Then inside `lib/instagram_clone_web/live/user_live/profile.ex` in our mount function, let's assign `my_action`:


```elixir
  @impl true
  def mount(%{"username" => username}, session, socket) do
    socket = assign_defaults(session, socket)
    user = Accounts.profile(username)
    my_action = get_action(user, socket.assigns.current_user)

    {:ok,
      socket
      |> assign(my_action: my_action)
      |> assign(user: user)
      |> assign(page_title: "#{user.full_name} (@#{user.username})")}
  end
```

Then open `lib/instagram_clone_web/live/user_live/profile.html.leex` and lets update the conditional to the following:

```html
<header class="flex justify-center px-10">
  <!-- Profile Picture Section -->
  <section class="w-1/4">
      <%= img_tag @user.avatar_url,
          class: "w-40 h-40 rounded-full object-cover object-center" %>
  </section>
  <!-- END Profile Picture Section -->

  <!-- Profile Details Section -->
  <section class="w-3/4">
    <div class="flex px-3 pt-3">
        <h1 class="truncate md:overflow-clip text-2xl md:text-2xl text-gray-500 mb-3"><%= @user.username %></h1>
        <span class="ml-11">
          <%= if @my_action in [:edit_profile] do %>
            <%= live_patch "Edit Profile",
                to: Routes.live_path(@socket, InstagramCloneWeb.UserLive.Settings),
                class: "py-1 px-2 border-2 rounded font-semibold hover:bg-gray-50" %>
          <% end %>

          <%= if @my_action in [:follow_component] do %>
            <%= live_component @socket,
                InstagramCloneWeb.UserLive.FollowComponent,
                id: @user.id,
                user: @user,
                current_user: @current_user %>
          <% end %>

          <%= if @my_action in [:login_btn] do %>
            <%= link "Follow", to: Routes.user_session_path(@socket, :new), class: "user-profile-follow-btn" %>
          <% end %>
        </span>
    </div>

    <div>
      <ul class="flex p-3">
          <li><b>0</b> Posts</li>
          <li class="ml-11"><b><%= @user.followers_count %></b> Followers</li>
          <li class="ml-11"><b><%= @user.following_count %></b> Following</li>
      </ul>
    </div>

    <div class="p-3">
      <h2 class="text-md text-gray-600 font-bold"><%= @user.full_name %></h2>
      <%= if @user.bio do %>
        <p class="max-w-full break-words"><%= @user.bio %></p>
      <% end %>
      <%= if @user.website do %>
        <%= link display_website_uri(@user.website),
          to: @user.website,
          target: "_blank", rel: "noreferrer",
          class: "text-blue-700" %>
      <% end %>
    </div>
  </section>
  <!-- END Profile Details Section -->
</header>

<section class="border-t-2 mt-5">
  <ul class="flex justify-center text-center space-x-20">
    <li class="pt-4 px-1 text-sm text-gray-600 border-t-2 border-black -mt-0.5">
       POSTS
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      IGTV
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      SAVED
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      TAGGED
    </li>
  </ul>
</section>
```

Let's work on displaying the following and followers, we are going to use modals for that, we will need to use the `handle_params()` function that is defined in a macro on every LiveView so let's take that function and properly assign it.

Open `lib/instagram_clone_web.ex` and delete the `handle_params()` from our `live_view()` macro:

```elixir
	  # DELETE THIS FUNCTION AND MOVE IT TO: 
	  #lib/instagram_clone_web/live/user_live/settings.ex
	  #lib/instagram_clone_web/live/user_live/pass_settings.ex
	  #lib/instagram_clone_web/live/page_live.ex
      @impl true
      def handle_params(_params, uri, socket) do
        {:noreply,
          socket
          |> assign(current_uri_path: URI.parse(uri).path)}
      end
```

**For now until we find another way we will need to manually assign the `current_uri_path` on each liveview, so let's keep that in mind we will have to remember it.**

Open `lib/instagram_clone_web/router.ex`: 

```elixir
	scope "/", InstagramCloneWeb do
	  pipe_through :browser
	  
	  live "/", PageLive,  :index
	  live "/:username", UserLive.Profile, :index # THIS LINE WAS UPDATED
	end

	scope "/", InstagramCloneWeb do
	  pipe_through [:browser, :require_authenticated_user]

	  get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email
	  live "/accounts/edit", UserLive.Settings
	  live "/accounts/password/change", UserLive.PassSettings
	  live "/:username/following", UserLive.Profile, :following  # THIS LINE WAS ADDED
	  live "/:username/followers", UserLive.Profile, :followers # THIS LINE WAS ADDED
	end
```

Open our navigation header `lib/instagram_clone_web/live/header_nav_component.html.leex` on line 56 let’s update our route:

```elixir
<%= live_patch to: Routes.user_profile_path(@socket, :index, @current_user.username)  do  %>
```

Update `lib/instagram_clone_web/live/user_live/profile.ex`:

```elixir
defmodule InstagramCloneWeb.UserLive.Profile do
  use InstagramCloneWeb, :live_view

  alias InstagramClone.Accounts
  alias InstagramCloneWeb.UserLive.FollowComponent

  @impl true
  def mount(%{"username" => username}, session, socket) do
    socket = assign_defaults(session, socket)
    user = Accounts.profile(username)

    {:ok,
      socket
      |> assign(user: user)
      |> assign(page_title: "#{user.full_name} (@#{user.username})")}
  end

  @impl true
  def handle_params(_params, uri, socket) do
    socket = socket |> assign(current_uri_path: URI.parse(uri).path)
    {:noreply, apply_action(socket, socket.assigns.live_action)}
  end

  @impl true
  def handle_info({FollowComponent, :update_totals, updated_user}, socket) do
    {:noreply, apply_msg_action(socket, socket.assigns.live_action, updated_user)}
  end

  defp apply_msg_action(socket, :follow_component, updated_user) do
    socket |> assign(user: updated_user)
  end

  defp apply_msg_action(socket, _, _updated_user) do
    socket
  end

  defp apply_action(socket, :index) do
	live_action = get_live_action(socket.assigns.user, socket.assigns.current_user)
	
    socket |> assign(live_action: live_action)
  end

  defp apply_action(socket, :following) do
    following = Accounts.list_following(socket.assigns.user)
    socket |> assign(following: following)
  end

  defp apply_action(socket, :followers) do
    followers = Accounts.list_followers(socket.assigns.user)
    socket |> assign(followers: followers)
  end

  defp get_live_action(user, current_user) do
    cond do
      current_user && current_user == user -> :edit_profile
      current_user -> :follow_component
      true -> :login_btn
    end
  end

end
```

Update `lib/instagram_clone_web/live/user_live/profile.html.leex`:


```html
<%= if @live_action == :following do %>
  <%= live_modal @socket, InstagramCloneWeb.UserLive.Profile.FollowingComponent,
    id: @user.id || :following,
    width:  "w-1/4",
    current_user: @current_user,
    following: @following,
    return_to: Routes.user_profile_path(@socket, :index, @user.username) %>
<% end %>

<%= if @live_action == :followers do %>
  <%= live_modal @socket, InstagramCloneWeb.UserLive.Profile.FollowersComponent,
    id: @user.id || :followers,
    width:  "w-1/4",
    current_user: @current_user,
    followers: @followers,
    return_to: Routes.user_profile_path(@socket, :index, @user.username) %>
<% end %>

<header class="flex justify-center px-10">
  <!-- Profile Picture Section -->
  <section class="w-1/4">
      <%= img_tag @user.avatar_url,
          class: "w-40 h-40 rounded-full object-cover object-center" %>
  </section>
  <!-- END Profile Picture Section -->

  <!-- Profile Details Section -->
  <section class="w-3/4">
    <div class="flex px-3 pt-3">
        <h1 class="truncate md:overflow-clip text-2xl md:text-2xl text-gray-500 mb-3"><%= @user.username %></h1>
        <span class="ml-11">
          <%= if @live_action == :edit_profile do %>
            <%= live_patch "Edit Profile",
                to: Routes.live_path(@socket, InstagramCloneWeb.UserLive.Settings),
                class: "py-1 px-2 border-2 rounded font-semibold hover:bg-gray-50" %>
          <% end %>

          <%= if @live_action == :follow_component do %>
            <%= live_component @socket,
                InstagramCloneWeb.UserLive.FollowComponent,
                id: @user.id,
                user: @user,
                current_user: @current_user %>
          <% end %>

          <%= if @live_action == :login_btn do %>
            <%= link "Follow", to: Routes.user_session_path(@socket, :new), class: "user-profile-follow-btn" %>
          <% end %>
        </span>
    </div>

    <div>
      <ul class="flex p-3">
          <li><b>0</b> Posts</li>
          <%= live_patch to: Routes.user_profile_path(@socket, :followers, @user.username) do %>
            <li class="ml-11"><b><%= @user.followers_count %></b> Followers</li>
          <% end %>
          <%= live_patch to: Routes.user_profile_path(@socket, :following, @user.username) do %>
            <li class="ml-11"><b><%= @user.following_count %></b> Following</li>
          <% end %>
      </ul>
    </div>

    <div class="p-3">
      <h2 class="text-md text-gray-600 font-bold"><%= @user.full_name %></h2>
      <%= if @user.bio do %>
        <p class="max-w-full break-words"><%= @user.bio %></p>
      <% end %>
      <%= if @user.website do %>
        <%= link display_website_uri(@user.website),
          to: @user.website,
          target: "_blank", rel: "noreferrer",
          class: "text-blue-700" %>
      <% end %>
    </div>
  </section>
  <!-- END Profile Details Section -->
</header>

<section class="border-t-2 mt-5">
  <ul class="flex justify-center text-center space-x-20">
    <li class="pt-4 px-1 text-sm text-gray-600 border-t-2 border-black -mt-0.5">
       POSTS
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      IGTV
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      SAVED
    </li>
    <li class="pt-4 px-1 text-sm text-gray-400">
      TAGGED
    </li>
  </ul>
</section>
```

Open `lib/instagram_clone_web/live/render_helpers.ex` and add the following to help us displayed modals:

```elixir
  import Phoenix.LiveView.Helpers

      @doc """
  Renders a component inside the `LiveviewPlaygroundWeb.ModalComponent` component.

  The rendered modal receives a `:return_to` option to properly update
  the URL when the modal is closed.
  The rendered modal also receives a `:width` option for the style width

  ## Examples

      <%= live_modal @socket, LiveviewPlaygroundWeb.PostLive.FormComponent,
        id: @post.id || :new,
        width: "w-1/2",
        post: @post,
        return_to: Routes.post_index_path(@socket, :index) %>
  """
  def live_modal(socket, component, opts) do
    path = Keyword.fetch!(opts, :return_to)
    width = Keyword.fetch!(opts,  :width)
    modal_opts = [id: :modal, return_to: path, width: width, component: component, opts: opts]
    live_component(socket, InstagramCloneWeb.ModalComponent, modal_opts)
  end
```

Create the modal component `lib/instagram_clone_web/live/modal_component.ex`:

```elixir
defmodule InstagramCloneWeb.ModalComponent do
  use InstagramCloneWeb, :live_component

  @impl true
  def render(assigns) do
    ~L"""
    <div
      class="fixed top-0 left-0 flex items-center justify-center w-full h-screen bg-black bg-opacity-40 z-50"
      phx-capture-click="close"
      phx-window-keydown="close"
      phx-key="escape"
      phx-target="<%= @myself %>"
      phx-page-loading>

      <div class="<%= @width %> h-auto bg-white rounded-xl shadow-xl">
        <%= live_patch raw("&times;"), to: @return_to, class: "float-right text-gray-500 text-4xl px-4" %>
        <%= live_component @socket, @component, @opts %>
      </div>
    </div>
    """
  end

    @impl true
  def handle_event("close", _, socket) do
    {:noreply, push_patch(socket, to: socket.assigns.return_to)}
  end
end
```

Create the followers component `lib/instagram_clone_web/live/user_live/followers_component.ex`:

```elixir
defmodule InstagramCloneWeb.UserLive.Profile.FollowersComponent do
  use InstagramCloneWeb, :live_component

  alias InstagramClone.Uploaders.Avatar
end

```

And the template `lib/instagram_clone_web/live/user_live/followers_component.html.leex`:

```html
<header class="bg-gray-50 p-2 border-b-2 rounded-t-xl">
  <h1 class="flex justify-center text-xl font-semibold">Followers</h1>
</header>

<%= for follow <- @followers do %>
  <div class="p-4">
    <div class="flex items-center">
      <%= live_redirect to: Routes.user_profile_path(@socket, :index, follow.follower.username) do %>
        <%= img_tag Avatar.get_thumb(follow.follower.avatar_url), class: "w-10 h-10 rounded-full object-cover object-center" %>
      <% end %>

      <div class="ml-3">
        <%= live_redirect  follow.follower.username,
          to: Routes.user_profile_path(@socket, :index, follow.follower.username),
          class: "font-semibold text-sm truncate text-gray-700 hover:underline" %>
        <h6 class="font-semibold text-sm truncate text-gray-400">
          <%= follow.follower.full_name %>
        </h6>
      </div>
      <%= if @current_user !== follow.follower do %>
        <span class="ml-auto">
          <%= live_component @socket,
            InstagramCloneWeb.UserLive.FollowComponent,
            id: follow.follower.id,
            user: follow.follower,
            current_user: @current_user %>
        </span>
      <% end %>
    </div>

  </div>
<% end %>
```

Create the following component `lib/instagram_clone_web/live/user_live/following_component.ex`:

```elixir
defmodule InstagramCloneWeb.UserLive.Profile.FollowingComponent do
  use InstagramCloneWeb, :live_component

  alias InstagramClone.Uploaders.Avatar
end

```

And the template `lib/instagram_clone_web/live/user_live/following_component.html.leex`:

```html
<header class="bg-gray-50 p-2 border-b-2 rounded-t-xl">
  <h1 class="flex justify-center text-xl font-semibold">Following</h1>
</header>
<%= for follow <- @following do %>
  <div class="p-4">
    <div class="flex items-center">
      <%= live_redirect to: Routes.user_profile_path(@socket, :index, follow.followed.username) do %>
        <%= img_tag Avatar.get_thumb(follow.followed.avatar_url), class: "w-10 h-10 rounded-full object-cover object-center" %>
      <% end %>

      <div class="ml-3">
        <%= live_redirect  follow.followed.username,
          to: Routes.user_profile_path(@socket, :index, follow.followed.username),
          class: "font-semibold text-sm truncate text-gray-700 hover:underline" %>
        <h6 class="font-semibold text-sm truncate text-gray-400">
          <%= follow.followed.full_name %>
        </h6>
      </div>
      <%= if @current_user !== follow.followed do %>
        <span class="ml-auto">
          <%= live_component @socket,
            InstagramCloneWeb.UserLive.FollowComponent,
            id: follow.followed.id,
            user: follow.followed,
            current_user: @current_user %>
        </span>
      <% end %>
    </div>
  </div>
<% end %>
```

Also we need to make a minor tweak in our avatar uploaders, open `lib/instagram_clone_web/live/uploaders/avatar.ex` and the following:

```elixir
  # This was added to return the default image when no avatar uploaded
  def get_thumb(avatar_url) when avatar_url == "/images/default-avatar.png" do
    avatar_url
  end

  def get_thumb(avatar_url) do
    file_name = String.replace_leading(avatar_url, "/uploads/", "")
    ["/#{@upload_directory_name}", "thumb_#{file_name}"] |> Path.join()
  end
```

&nbsp;

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2amlv9wd6g5ni4mr7uab.gif)

&nbsp;

Let's do some minor tweaks to our header nav menu to not have to assign the current_uri_path on each LiveView, and to our `page_live` component to set the form inside the component instead of the parent LiveView. 

Open `lib/instagram_clone_web/live/page_live.ex` and update the file to the following:

```elixir
defmodule InstagramCloneWeb.PageLive do
  use InstagramCloneWeb, :live_view

  @impl true
  def mount(_params, session, socket) do
    socket = assign_defaults(session, socket)
    {:ok, socket}
  end

  @impl true
  def handle_params(_params, _uri, socket) do
    {:noreply,
      socket
      |> assign(live_action: apply_action(socket.assigns.current_user))}
  end

  defp apply_action(current_user) do
    if !current_user, do: :root_path
  end
end
```

Update `lib/instagram_clone_web/live/page_live_component.ex` to the following:

```elixir
defmodule InstagramCloneWeb.PageLiveComponent do
  use InstagramCloneWeb, :live_component

  alias InstagramClone.Accounts
  alias InstagramClone.Accounts.User

  @impl true
  def mount(socket) do
    changeset = Accounts.change_user_registration(%User{})
    {:ok,
      socket
      |> assign(changeset: changeset)
      |> assign(trigger_submit: false)}
  end

  @impl true
  def handle_event("validate", %{"user" => user_params}, socket) do
    changeset =
      %User{}
      |> User.registration_changeset(user_params)
      |> Map.put(:action, :validate)
    {:noreply, socket |> assign(changeset: changeset)}
  end

  def handle_event("save", _, socket) do
    {:noreply, assign(socket, trigger_submit: true)}
  end
end
```

Inside `lib/instagram_clone_web/live/page_live_component.html.leex` on line 5 add a target to the form:

```elixir
  <%= f = form_for @changeset, Routes.user_registration_path(@socket, :create),
    phx_change: "validate",
    phx_submit: "save",
    phx_target: @myself, # <-- THIS LINE WAS ADDED
    phx_trigger_action: @trigger_submit,
    class: "flex flex-col space-y-4 w-full px-6" %>
```

Update `lib/instagram_clone_web/live/page_live.html.leex` to the following:

```elixir
<%= if @current_user do %>
  <h1>User Logged In Homepage</h1>
<% else %>
  <%= live_component @socket,
    InstagramCloneWeb.PageLiveComponent,
    id: 1 %>
<% end %>
```

Open `lib/instagram_clone_web/templates/layout/live.html.leex` and update the top logic to the following:

```elixir
<%= if @current_user do %>
  <%= live_component @socket, InstagramCloneWeb.HeaderNavComponent, current_user: @current_user %>

<% else %>
  <%= if @live_action !== :root_path do %>
    <%= live_component @socket, InstagramCloneWeb.HeaderNavComponent, current_user: @current_user %>
  <% end %>
<% end %>
```

Now we won't need `@curent_uri_path` on each liveview, we can delete it from `lib/instagram_clone_web/live/user_live/profile.ex` on line 20 inside `handle_params()` :

```elixir
  @impl true
  def handle_params(_params, uri, socket) do
    socket = socket |> assign(current_uri_path: URI.parse(uri).path) # <-- DELETE THIS LINE
    {:noreply, apply_action(socket, socket.assigns.live_action)}
  end
```

Making this part was harder than I anticipated, it's getting a little challenging, this application is not as easy as we might think to get it right. My perfectionism got the best of me, that's why it took me longer to release it, I was ignorant on some things that I had to figure out, but definitely, I enjoyed the process and learned a ton. In the next part, we will work with user's posts.

&nbsp;

I really appreciate your time, thank you so much for reading.

&nbsp;

### CHECKOUT THE [INSTAGRAM CLONE GITHUB REPO](https://github.com/elixirprogrammer/InstagramClonePETAL)

&nbsp;
&nbsp;
&nbsp;

[![Join The Elixir Army](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cv2q172ztuekdtw82vo9.png)](https://elixirprogrammer.com)