Let's Build An Instagram Clone With The PETAL(Phoenix, Elixir, TailwindCSS, AlpineJS, LiveView) Stack [PART 2]
Tags: elixir, webdev, programming, tutorial
Published: 2021-04-16 05:13:51

In [part 1](https://elixirprogrammer.com/learn/lets-build-an-instagram-clone-with-the-petal-phoenix-elixir-tailwindcss-alpinejs-liveview-stack) we got everything set up and with our base layout ready to go let's start working on user settings. You can catch up with the [Instagram Clone GitHub Repo](https://github.com/elixirprogrammer/InstagramClonePETAL).

### User Settings

Let's start by creating our routes, open `lib/instagram_clone_web/router.ex` and let's add the followings 2 routes under `:require_authenticated_user` scope:
```elixir
  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
  end
``` 
Then we need to create those liveview files, create a folder named `user_live` under `lib/instagram_clone_web/live` inside that folder add the following 4 files:

`lib/instagram_clone_web/live/user_live/settings.ex`
`lib/instagram_clone_web/live/user_live/settings.html.leex`
`lib/instagram_clone_web/live/user_live/pass_settings.ex`
`lib/instagram_clone_web/live/user_live/pass_settings.html.leex`

In our navigation header, we need to link to that new route, open `lib/instagram_clone_web/live/header_nav_component.html.leex` on line 60 add the following to the `Settings` `live_patch to:`

```html
<%= live_patch to:  Routes.live_path(@socket, InstagramCloneWeb.UserLive.Settings)  do  %>
  <li  class="py-2 px-4 hover:bg-gray-50">Settings</li>
<%  end  %>
```
Now when we visit that link we should have an error because the files are empty so open `lib/instagram_clone_web/live/user_live/settings.ex` and add the following:

```elixir
defmodule  InstagramCloneWeb.UserLive.Settings  do

  use InstagramCloneWeb,  :live_view

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

    {:ok, socket}
  end

end
```

Now we should have a blank page just with the top nav bar, so let's go to work.

We are going to need the `Accounts` and `User` contexts, we will alias them and assign the changeset, our file should look like the following:

```elixir
defmodule  InstagramCloneWeb.UserLive.Settings  do

  use InstagramCloneWeb,  :live_view

  alias InstagramClone.Accounts
  alias InstagramClone.Accounts.User

  @impl true
  def  mount(_params, session, socket) do
    socket =  assign_defaults(session, socket)
    changeset = Accounts.change_user(socket.assigns.current_user)

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

end
```

We need to add the `change_user()` function to our `Accounts` context, open `lib/instagram_clone/accounts.ex` and below `change_user_registration()` function add the following:

```elixir
...

def  change_user(user, attrs \\  %{}) do
  User.registration_changeset(user, attrs,  register_user:  false)
end

...
```

Open `lib/instagram_clone_web/live/user_live/settings.html.leex` and let's add our form to our template:

```html
<section class="border-2 flex" x-data="{username: '<%= @current_user.username %>'}">

  <div class="w-full py-8">
    <!-- Profile Photo -->
    <div class="flex items-center">
      <div class="w-1/3">
        <%= img_tag @current_user.avatar_url, class: "ml-auto w-10 h-10 rounded-full object-cover object-center" %>
      </div>
      <div class="w-full pl-8">
        <h1 x-text="username" class="leading-none font-semibold text-lg truncate text-gray-500"></h1>
      </div>
    </div>
    <!-- END PROFILE PHOTO -->

    <%= f = form_for @changeset, "#",
      phx_change: "validate",
      phx_submit: "save",
      class: "space-y-8 md:space-y-10" %>

      <div class="flex items-center">
        <%= label f, :full_name, class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= text_input f, :full_name, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 focus:ring-transparent focus:border-black", autocomplete: "off" %>
          <%= error_tag f, :full_name, class:  "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <%= label f, :username, class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= text_input f, :username, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 focus:ring-transparent focus:border-black", x_model: "username", autocomplete: "off" %>
          <%= error_tag f, :username, class:  "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <%= label f, :website, class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= text_input f, :website, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 focus:ring-transparent focus:border-black", autocomplete: "off" %>
          <%= error_tag f, :website, class:  "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <%= label f, :bio, class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= textarea f, :bio, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 focus:ring-transparent focus:border-black", rows: 3, autocomplete: "off" %>
          <%= error_tag f, :bio, class:  "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <%= label f, :email, class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= email_input f, :email, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 shadow-sm focus:ring-transparent focus:border-black", autocomplete: "off" %>
          <%= error_tag f, :email, class:  "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <label class="block w-1/3 font-semibold text-right"></label>
        <div class="w-full pl-8 pr-20">
          <%= submit "Submit", phx_disable_with: "Saving...", class: "w-16 py-1 px-1 border-none shadow rounded font-semibold text-sm text-gray-50 hover:bg-light-blue-600 bg-light-blue-500 cursor-pointer" %>
        </div>
      </div>
    </form>
  </div>
</section>
```
We added the base layout of our form and the username heading is updated when you type in the username input with AlpineJs. Now we need to add the `validate()` and `save()` functions to our `lib/instagram_clone_web/live/user_live/settings.ex` liveview document, but let's first assign our `:page_title` to our mount function:

```elixir
  @impl true
  def mount(_params, session, socket) do
    socket = assign_defaults(session, socket)
    changeset = Accounts.change_user(socket.assigns.current_user)

    {:ok,
      socket
      |> assign(changeset: changeset)
      |> assign(page_title: "Edit Profile")} #This was added
  end
```

Then open `lib/instagram_clone_web/templates/layout/root.html.leex` and update the page title suffix:
```elixir
<%= live_title_tag assigns[:page_title]  ||  "InstagramClone",  suffix:  " · InstagramClone"  %>
```

Now let's add the functions to handle the form to our `lib/instagram_clone_web/live/user_live/settings.ex`:

```elixir
  @impl true
  def handle_event("validate", %{"user" => user_params}, socket) do
    changeset =
      socket.assigns.current_user
      |> Accounts.change_user(user_params)
      |> Map.put(:action, :validate)

    {:noreply, socket |> assign(changeset: changeset)}
  end

  @impl true
  def handle_event("save", %{"user" => user_params}, socket) do
    case Accounts.update_user(socket.assigns.current_user, user_params) do
      {:ok, _user} ->
        {:noreply,
          socket
          |> put_flash(:info, "User updated successfully")
          |> push_redirect(to: Routes.live_path(socket, InstagramWeb.UserLive.Settings))}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign(socket, :changeset, changeset)}
    end
  end
```

Now we need to add our `update_user()` function to our `Accounts` context:

```elixir
...

  def update_user(user, attrs) do
    user
    |> User.registration_changeset(attrs, register_user: false)
    |> Repo.update()
  end

...
```

Our unique constraint for username is not working because we didn't add a unique index to our migration so let's do that now. In our terminal let's generate a migration `$ mix ecto.gen.migration add_users_unique_username_index`, then open the migration that was generated `priv/repo/migrations/20210414220125_add_users_unique_username_index.exs` and add the following:

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

  def change do
    create unique_index(:users, [:username])
  end
end
```

Then get back to our terminal and run the migration `$ mix ecto.migrate`

Now let's update our registration changeset in `lib/instagram_clone/accounts/user.ex` with `unsafe_validate_unique(:username, InstagramClone.Repo)`:

```elixir
...

  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) # --> This was added
    |> validate_length(:full_name, min: 4, max: 30)
    |> validate_email()
    |> validate_password(opts)
 end

...
```

Also while testing I realize that I made a mistake trying to delay the live validation with `:timer.sleep(9000)` in our `lib/instagram_clone_web/live/page_live.ex` so let's just remove that line from our `validate()` function because it creates conflicts with the form:

```elixir
  @impl true
  def handle_event("validate", %{"user" => user_params}, socket) do
    changeset =
      %User{}
      |> User.registration_changeset(user_params)
      |> Map.put(:action, :validate)
    #:timer.sleep(9000) <-- REMOVE THIS LINE
    {:noreply, socket |> assign(changeset: changeset)}
  end
```

&nbsp;

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1fhue2un1dgo9d9lnb8u.jpg)
&nbsp;

With that done we should be able to edit the profile with no problem, so now let's work on the avatar file upload.

### Avatar Uploads

Open `lib/instagram_clone_web/live/user_live/settings.ex` and let's allow uploads in our liveview, the new updated file should look like the following:

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

  alias InstagramClone.Accounts
  alias InstagramClone.Accounts.User
  #Files extensions accepted to be uploaded
  @extension_whitelist ~w(.jpg .jpeg .png)

  @impl true
  def mount(_params, session, socket) do
    socket = assign_defaults(session, socket)
    changeset = Accounts.change_user(socket.assigns.current_user)

    {:ok,
      socket
      |> assign(changeset: changeset)
      |> assign(page_title: "Edit Profile")
      |> allow_upload(:avatar_url,
      accept: @extension_whitelist,
      max_file_size: 9_000_000,
      progress: &handle_progress/3,#Function that will handle automatic uploads
      auto_upload: true)}
  end

  @impl true
  def handle_event("validate", %{"user" => user_params}, socket) do
    changeset =
      socket.assigns.current_user
      |> Accounts.change_user(user_params)
      |> Map.put(:action, :validate)

    {:noreply, socket |> assign(changeset: changeset)}
  end
  # Updates the socket when the upload form changes, triguers handle_progress()
  def handle_event("upload_avatar", _params, socket) do
    {:noreply, socket}
  end

  @impl true
  def handle_event("save", %{"user" => user_params}, socket) do
    case Accounts.update_user(socket.assigns.current_user, user_params) do
      {:ok, _user} ->
        {:noreply,
          socket
          |> put_flash(:info, "User updated successfully")
          |> push_redirect(to: Routes.live_path(socket, InstagramCloneWeb.UserLive.Settings))}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign(socket, :changeset, changeset)}
    end
  end
  # This will handle the upload
  defp handle_progress(:avatar_url, entry, socket) do

  end
end
```

Open `lib/instagram_clone_web/live/user_live/settings.html.leex` and let's add our upload form below our username heading, with the `@uploads` that is being assigned to our socket:

```html
<section class="border-2 flex" x-data="{username: '<%= @current_user.username %>'}">

  <div class="w-full py-8">
    <%= for {_ref, err} <- @uploads.avatar_url.errors do %>
      <p class="text-red-500 w-full text-center">
        <%= Phoenix.Naming.humanize(err) %>
      </p>
    <% end %>
    <!-- Profile Photo -->
    <div class="flex items-center">
      <div class="w-1/3">
        <%= img_tag @current_user.avatar_url, class: "ml-auto w-10 h-10 rounded-full object-cover object-center" %>
      </div>
      <div class="w-full pl-8">
        <h1 x-text="username" class="leading-none font-semibold text-lg truncate text-gray-500"></h1>
        
        <!-- THIS WAS ADDED -->
        <div  class="relative">
          <%= form_for @changeset,  "#",
            phx_change:  "upload_avatar"  %>
            <%= live_file_input @uploads.avatar_url,  class:  "cursor-pointer relative block opacity-0 z-40 -left-24"  %>
            <div  class="text-center absolute top-0 left-0 m-auto">
              <span  class="font-semibold text-sm text-light-blue-500">
                Change Profile Photo
              </span>
            </div>
          </form>
        </div>
        <!-- THIS WAS ADDED END -->
        
      </div>
    </div>
    <!-- END PROFILE PHOTO -->

    <%= f = form_for @changeset, "#",
      phx_change: "validate",
      phx_submit: "save",
      class: "space-y-8 md:space-y-10" %>

      <div class="flex items-center">
        <%= label f, :full_name, class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= text_input f, :full_name, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 focus:ring-transparent focus:border-black", autocomplete: "off" %>
          <%= error_tag f, :full_name, class:  "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <%= label f, :username, class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= text_input f, :username, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 focus:ring-transparent focus:border-black", x_model: "username", autocomplete: "off" %>
          <%= error_tag f, :username, class:  "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <%= label f, :website, class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= text_input f, :website, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 focus:ring-transparent focus:border-black", autocomplete: "off" %>
          <%= error_tag f, :website, class:  "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <%= label f, :bio, class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= textarea f, :bio, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 focus:ring-transparent focus:border-black", rows: 3, autocomplete: "off" %>
          <%= error_tag f, :bio, class:  "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <%= label f, :email, class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= email_input f, :email, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 shadow-sm focus:ring-transparent focus:border-black", autocomplete: "off" %>
          <%= error_tag f, :email, class:  "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <label class="block w-1/3 font-semibold text-right"></label>
        <div class="w-full pl-8 pr-20">
          <%= submit "Submit", phx_disable_with: "Saving...", class: "w-16 py-1 px-1 border-none shadow rounded font-semibold text-sm text-gray-50 hover:bg-light-blue-600 bg-light-blue-500 cursor-pointer" %>
        </div>
      </div>
    </form>
  </div>
</section>
```

Now let's create a module that would help us handle avatar uploads. Under `lib/instagram_clone_web/live` add a folder named `uploaders`, and inside that folder add a file named `avatar.ex` . We're going to be resizing the avatars so let's add the Mogrify dependency to handle it, make sure to have ImageMagick installed, open `mix.exs` and add to our project dependencies `{:mogrify, "~> 0.8.0"}`, then in our terminal `$ mix deps.get && mix deps.compile`.

Now open `lib/instagram_clone_web/live/uploaders/avatar.ex` and add the following:

```elixir
defmodule InstagramClone.Uploaders.Avatar do
  alias InstagramCloneWeb.Router.Helpers, as: Routes
  
  # We are going to upload locally so this would be the name of the folder
  @upload_directory_name "uploads"
  @upload_directory_path "priv/static/uploads"
  
  # Returns the extensions associated with a given MIME type.
  defp ext(entry) do
    [ext | _] = MIME.extensions(entry.client_type)
    ext
  end
  
  # Returns the url path
  def get_avatar_url(socket, entry) do
    Routes.static_path(socket, "/#{@upload_directory_name}/#{entry.uuid}.#{ext(entry)}")
  end

  def update(socket, old_url, entry) do
    # Creates the upload directry path if not exists 
    if !File.exists?(@upload_directory_path), do: File.mkdir!(@upload_directory_path)
    
    # Consumes an individual uploaded entry
    Phoenix.LiveView.consume_uploaded_entry(socket, entry, fn %{} = meta ->
      # Destination paths for avatar thumbs
      dest = Path.join(@upload_directory_path, "#{entry.uuid}.#{ext(entry)}")
      dest_thumb = Path.join(@upload_directory_path, "thumb_#{entry.uuid}.#{ext(entry)}")
      
      # meta.path is the temporary file path
      mogrify_thumbnail(meta.path, dest, 300)
      mogrify_thumbnail(meta.path, dest_thumb, 150)
      
      # Removes Old Urls Paths
      rm_file(old_url)
      old_url |> get_thumb() |> rm_file()
    end)

    :ok
  end

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

  def rm_file(old_avatar_url) do
    url = String.replace_leading(old_avatar_url, "/uploads/", "")
    path = [@upload_directory_path, url] |> Path.join()

    if File.exists?(path),  do: File.rm!(path)
  end
  
  # Resize the file with a given path, destination, and size
  defp mogrify_thumbnail(src_path, dst_path, size) do
    try do
      Mogrify.open(src_path)
      |> Mogrify.resize_to_limit("#{size}x#{size}")
      |> Mogrify.save(path: dst_path)
    rescue
      File.Error -> {:error, :invalid_src_path}
      error -> {:error, error}
    else
      _image -> {:ok, dst_path}
    end
  end

end
```

Open `lib/instagram_clone_web/live/user_live/settings.ex` alias your newly created `Avatar` module at the top of our file `alias InstagramClone.Uploaders.Avatar` and update our `handle_progress()` function with the following:

```elixir
  defp handle_progress(:avatar_url, entry, socket) do
	# If file is already uploaded to tmp folder
    if entry.done? do
      avatar_url = Avatar.get_avatar_url(socket, entry)
      user_params = %{"avatar_url" => avatar_url}
      case Accounts.update_user(socket.assigns.current_user, user_params) do
        {:ok, _user} ->
          Avatar.update(socket, socket.assigns.current_user.avatar_url, entry)
          @doc """
            We have to update the current user and assign it back to the socket 
            to get the header nav thumbnail automatically updated
          """
          current_user = Accounts.get_user!(socket.assigns.current_user.id)
          {:noreply,
            socket
            |> put_flash(:info, "Avatar updated successfully")
            |> assign(current_user: current_user)}
        {:error, %Ecto.Changeset{} = changeset} ->
          {:noreply, assign(socket, :changeset, changeset)}
      end
    else
      {:noreply, socket}
    end
  end
```

Lastly, we need to serve the files from our uploads directory that's going to be created, open `lib/instagram_clone_web/endpoint.ex` and update line 27 in our Static Plug:

```elixir
only:  ~w(css fonts images js favicon.ico robots.txt uploads)
```
Now everything should work just fine, but we are uploading a thumbnail so let's use it in our templates, open `lib/instagram_clone_web/live/header_nav_component.html.leex` and update line 43 to use our thumbnail URL instead:

```elixir
<%= img_tag InstagramClone.Uploaders.Avatar.get_thumb(@current_user.avatar_url), class:  "w-full h-full object-cover object-center"  %>
```

Also open `lib/instagram_clone_web/live/user_live/settings.html.leex` and update line 7 to use our thumbnail also instead:

```elixir
<%= img_tag Avatar.get_thumb(@current_user.avatar_url),  class:  "ml-auto w-10 h-10 rounded-full object-cover object-center"  %>
```

### Password Change Settings

Now the only thing left is changing the password, we are going to need a side navbar for that, so let's create a component to handle it because it will get share with the password change LiveView.	 Under `lib/instagram_clone_web/live/user_live` add the following 2 files:

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

In `lib/instagram_clone_web/live/user_live/settings_sidebar_component.ex` add the following:

```elixir
defmodule  InstagramCloneWeb.UserLive.SettingsSidebarComponent  do
  use InstagramCloneWeb,  :live_component
end
```

To `lib/instagram_clone_web/live/user_live/settings_sidebar_component.html.leex` add the following:

```html
<div class="w-1/4 border-r-2">
  <ul>
    <%= live_patch content_tag(:li, "Edit Profile", class: "p-4 #{selected_link?(@current_uri_path,  @settings_path)}"),  to:  @settings_path %>
    <%= live_patch content_tag(:li, "Change Password", class: "p-4 #{selected_link?(@current_uri_path,  @pass_settings_path)}"),  to:  @pass_settings_path %>
  </ul>
</div>
```

Create a file named `render_helpers.ex` under `lib/instagram_clone_web/live`. Open `lib/instagram_clone_web/live/render_helpers.ex` and the following::

```elixir
defmodule  InstagramCloneWeb.RenderHelpers  do

  def  selected_link?(current_uri, menu_link) when current_uri == menu_link do
    "border-l-2 border-black -ml-0.5 text-gray-900 font-semibold"
  end

  

  def  selected_link?(_current_uri,  _menu_link) do
    "hover:border-l-2 -ml-0.5 hover:border-gray-300 hover:bg-gray-50"
  end

end
```

Those functions will help us to get the right styles for our links in our side navbar. Now we need to make those functions available in our templates, open `lib/instagram_clone_web.ex` and update the view helpers function to the following:

```elixir
  defp view_helpers do
    quote do
      # Use all HTML functionality (forms, tags, etc)
      use Phoenix.HTML

      # Import LiveView helpers (live_render, live_component, live_patch, etc)
      import Phoenix.LiveView.Helpers

      # Import basic rendering functionality (render, render_layout, etc)
      import Phoenix.View

      import InstagramCloneWeb.ErrorHelpers
      import InstagramCloneWeb.Gettext
      import InstagramCloneWeb.RenderHelpers # <-- THIS LINE WAS ADDED
      alias InstagramCloneWeb.Router.Helpers, as: Routes
    end
  end
```

Let's assign our paths to the socket, open `lib/instagram_clone_web/live/user_live/settings.ex` and in our mount add the following:

```elixir
  def mount(_params, session, socket) do
    socket = assign_defaults(session, socket)
    changeset = Accounts.change_user(socket.assigns.current_user)
    # THIS WAS ADDED
    settings_path = Routes.live_path(socket, __MODULE__)
    pass_settings_path = Routes.live_path(socket, InstagramCloneWeb.UserLive.PassSettings)


    {:ok,
      socket
      |> assign(changeset: changeset)
      |> assign(page_title: "Edit Profile")
      |> assign(settings_path: settings_path, pass_settings_path: pass_settings_path)# <-- THIS WAS ADDED
      |> allow_upload(:avatar_url,
      accept: @extension_whitelist,
      max_file_size: 9_000_000,
      progress: &handle_progress/3,
      auto_upload: true)}
  end
```

Open `lib/instagram_clone_web/live/user_live/settings.html.leex` inside the section tag at the top just below the start of the tag, let's insert our component:

```elixir
<%= live_component @socket, InstagramCloneWeb.UserLive.SettingsSidebarComponent,  
  settings_path:  @settings_path,  
  pass_settings_path:  @pass_settings_path,  
  current_uri_path:  @current_uri_path %>
```

Open `lib/instagram_clone_web/live/user_live/pass_settings.ex` add the following:

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

  def mount(_params, session, socket) do
    socket = assign_defaults(session, socket)
    settings_path = Routes.live_path(socket, InstagramCloneWeb.UserLive.Settings)
    pass_settings_path = Routes.live_path(socket, __MODULE__)

    {:ok,
      socket
      |> assign(settings_path: settings_path, pass_settings_path: pass_settings_path)}
  end
end
```

Then open `lib/instagram_clone_web/live/user_live/pass_settings.html.leex` add the following:

```elixir
<section class="border-2 flex">
  <%= live_component @socket, InstagramCloneWeb.UserLive.SettingsSidebarComponent,
    settings_path: @settings_path,
    pass_settings_path: @pass_settings_path,
    current_uri_path: @current_uri_path %>
</section>
```

&nbsp;

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

&nbsp;

Let's add the form to `lib/instagram_clone_web/live/user_live/pass_settings.html.leex` :

```html
<section class="border-2 flex">
  <%= live_component @socket, InstagramCloneWeb.UserLive.SettingsSidebarComponent,
    settings_path: @settings_path,
    pass_settings_path: @pass_settings_path,
    current_uri_path: @current_uri_path %>

  <div class="w-full py-5">
    <!-- Profile Photo -->
    <div class="flex items-center">
      <div class="w-1/3">
        <%= img_tag Avatar.get_thumb(@current_user.avatar_url), class: "ml-auto w-10 h-10 rounded-full object-cover object-center" %>
      </div>
      <div class="w-full pl-8">
        <h1 class="font-semibold text-xl truncate text-gray-600"><%= @current_user.username %></h1>
      </div>
    </div>
    <!-- End Profile Photo -->
    <%= f = form_for @changeset, "#",
      phx_submit: "save",
      class: "space-y-5 md:space-y-8"  %>

      <div class="md:flex items-center">
        <%= label f, :old_password, "Old Password", class: "w-1/3 text-right font-semibold", for: "current_password_for_password" %>
        <div class="w-full pl-8 pr-20">
          <%= password_input f, :current_password, required: true, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 focus:ring-transparent focus:border-black" %>
          <%= error_tag f, :current_password, class: "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <%= label f, :password, "New Password", class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= password_input f, :password, required: true, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 focus:ring-transparent focus:border-black" %>
          <%= error_tag f, :password, class: "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="md:flex items-center">
        <%= label f, :password_confirmation, "Confirm New Password", class: "w-1/3 text-right font-semibold" %>
        <div class="w-full pl-8 pr-20">
          <%= password_input f, :password_confirmation, required: true, class: "w-4/6 rounded p-1 text-semibold text-gray-600 border-gray-300 focus:ring-transparent focus:border-black" %>
          <%= error_tag f, :password_confirmation, class: "text-red-700 text-sm block" %>
        </div>
      </div>

      <div class="flex items-center">
        <label class="w-1/3"></label>
        <div class="w-full pl-8 pr-20">
          <%= submit "Change Password", phx_disable_with: "Saving...", class: "py-1 px-2 border-none shadow rounded font-semibold text-sm text-gray-50 hover:bg-light-blue-600 bg-light-blue-500 cursor-pointer" %>
        </div>
      </div>

      <div class="flex items-center">
        <label class="w-1/3"></label>
        <div class="w-full pl-8 pr-20 text-right">
          <%= link "Forgot Password?", to: Routes.user_reset_password_path(@socket, :new), class: "font-semibold text-xs hover:text-light-blue-600 text-light-blue-500 cursor-pointer hover:underline" %>
        </div>
      </div>
    </form>
  </div>
</section>
```

Finally update `lib/instagram_clone_web/live/user_live/pass_settings.ex` to the following:

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

  alias InstagramClone.Accounts
  alias InstagramClone.Accounts.User
  alias InstagramClone.Uploaders.Avatar

  @impl true
  def mount(_params, session, socket) do
    socket = assign_defaults(session, socket)
    settings_path = Routes.live_path(socket, InstagramCloneWeb.UserLive.Settings)
    pass_settings_path = Routes.live_path(socket, __MODULE__)
    user = socket.assigns.current_user

    {:ok,
      socket
      |> assign(settings_path: settings_path, pass_settings_path: pass_settings_path)
      |> assign(:page_title, "Change Password")
      |> assign(changeset: Accounts.change_user_password(user))}
  end

  @impl true
  def handle_event("save", %{"user" => params}, socket) do
    %{"current_password" => password} = params
    case Accounts.update_user_password(socket.assigns.current_user, password, params) do
      {:ok, _user} ->
        {:noreply,
          socket
          |> put_flash(:info, "Password updated successfully.")
          |> push_redirect(to: socket.assigns.pass_settings_path)}

      {:error, changeset} ->
        {:noreply, assign(socket, :changeset, changeset)}
    end
  end
end
```

Go to `lib/instagram_clone/accounts.ex` on line 208, and update `update_user_password()` to the following:

```elixir
def  update_user_password(user, password, attrs) do
  user
  |> User.password_changeset(attrs)
  |> User.validate_current_password(password)
  |> Repo.update()
end
```

&nbsp;

This is delightful, I'm really enjoying it. In the next part let's work on user's profiles. Let me know what you think in the comments down below, I really appreciate your time, thank you so much for reading.

&nbsp;

### CHECK OUT 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)