diff --git a/lib/link_shortener_web/components/layouts/app.html.heex b/lib/link_shortener_web/components/layouts/app.html.heex
index 29724f4..d635743 100644
--- a/lib/link_shortener_web/components/layouts/app.html.heex
+++ b/lib/link_shortener_web/components/layouts/app.html.heex
@@ -9,7 +9,7 @@
diff --git a/lib/link_shortener_web/live/link_live/form_component.ex b/lib/link_shortener_web/live/link_live/form_component.ex
new file mode 100644
index 0000000..6fb14b0
--- /dev/null
+++ b/lib/link_shortener_web/live/link_live/form_component.ex
@@ -0,0 +1,98 @@
+defmodule LinkShortenerWeb.LinkLive.FormComponent do
+ use LinkShortenerWeb, :live_component
+
+ alias LinkShortener.Links
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ <%= @title %>
+ <:subtitle>Use this form to manage link records.
+
+
+ <.simple_form
+ for={@form}
+ id="link-form"
+ phx-target={@myself}
+ phx-change="validate"
+ phx-submit="save"
+ >
+ <.input field={@form[:name]} type="text" label="Name" placeholder="Enter a name here" />
+ <.input field={@form[:url]} type="text" label="Url" placeholder="Enter an url here" />
+ <.input field={@form[:shorten]} type="text" label="Shorten" placeholder="Enter a shorten here" />
+ <:actions>
+ <.button phx-disable-with="Saving...">Save Link
+
+
+
+ """
+ end
+
+ @impl true
+ def update(%{link: link} = assigns, socket) do
+ {:ok,
+ socket
+ |> assign(assigns)
+ |> assign_new(:form, fn ->
+ to_form(Links.edit_one(link))
+ end)}
+ end
+
+ @impl true
+ def handle_event("validate", %{"link" => link_params}, socket) do
+ link_params = with_user_id(link_params, socket.assigns.current_user)
+ changeset = Links.edit_one(socket.assigns.link, link_params)
+ {:noreply, assign(socket, form: to_form(changeset, action: :validate))}
+ end
+
+ def handle_event("save", %{"link" => link_params}, socket) do
+ save_link(socket, socket.assigns.action, link_params)
+ end
+
+ defp save_link(socket, :edit, link_params) do
+ case Links.update_one(socket.assigns.link, link_params) do
+ {:ok, link} ->
+ notify_parent({:saved, link})
+
+ {:noreply,
+ socket
+ |> put_flash(:info, "Link updated successfully")
+ |> push_patch(to: socket.assigns.patch)}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign(socket, form: to_form(changeset))}
+ end
+ end
+
+ defp save_link(socket, :new, link_params) do
+ link_params =
+ link_params
+ |> with_user_id(socket.assigns.current_user)
+
+ case Links.insert_one(link_params) do
+ {:ok, link} ->
+ notify_parent({:saved, link})
+
+ {:noreply,
+ socket
+ |> put_flash(:info, "Link created successfully")
+ |> push_patch(to: socket.assigns.patch)}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign(socket, form: to_form(changeset))}
+ end
+ end
+
+ defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
+
+ defp with_user_id(params, current_user) do
+ if Map.has_key?(params, "user_id") do
+ params
+ else
+ params
+ |> Map.put("user_id", current_user.id)
+ end
+ end
+end
diff --git a/lib/link_shortener_web/live/link_live/index.ex b/lib/link_shortener_web/live/link_live/index.ex
new file mode 100644
index 0000000..91e0792
--- /dev/null
+++ b/lib/link_shortener_web/live/link_live/index.ex
@@ -0,0 +1,48 @@
+defmodule LinkShortenerWeb.LinkLive.Index do
+ use LinkShortenerWeb, :live_view
+
+ alias LinkShortener.Links
+ alias LinkShortener.Links.Link
+
+ @impl true
+ def mount(_params, _session, socket) do
+ current_user = socket.assigns.current_user
+ {:ok, stream(socket, :links, Links.get_all_by_user(current_user))}
+ end
+
+ @impl true
+ def handle_params(params, _url, socket) do
+ {:noreply, apply_action(socket, socket.assigns.live_action, params)}
+ end
+
+ defp apply_action(socket, :edit, %{"id" => id}) do
+ socket
+ |> assign(:page_title, "Edit Link")
+ |> assign(:link, Links.get_one!(id))
+ end
+
+ defp apply_action(socket, :new, _params) do
+ socket
+ |> assign(:page_title, "New Link")
+ |> assign(:link, %Link{})
+ end
+
+ defp apply_action(socket, :index, _params) do
+ socket
+ |> assign(:page_title, "Listing Links")
+ |> assign(:link, nil)
+ end
+
+ @impl true
+ def handle_info({LinkShortenerWeb.LinkLive.FormComponent, {:saved, link}}, socket) do
+ {:noreply, stream_insert(socket, :links, link)}
+ end
+
+ @impl true
+ def handle_event("delete", %{"id" => id}, socket) do
+ link = Links.get_one!(id)
+ {:ok, _} = Links.delete_one(link)
+
+ {:noreply, stream_delete(socket, :links, link)}
+ end
+end
diff --git a/lib/link_shortener_web/live/link_live/index.html.heex b/lib/link_shortener_web/live/link_live/index.html.heex
new file mode 100644
index 0000000..34ae04c
--- /dev/null
+++ b/lib/link_shortener_web/live/link_live/index.html.heex
@@ -0,0 +1,44 @@
+<.header>
+ Listing Links
+ <:actions>
+ <.link patch={~p"/links/new"}>
+ <.button>New Link
+
+
+
+
+<.table
+ id="links"
+ rows={@streams.links}
+ row_click={fn {_id, link} -> JS.navigate(~p"/links/#{link}") end}
+>
+ <:col :let={{_id, link}} label="Name"><%= link.name %>
+ <:col :let={{_id, link}} label="Url"><%= link.url %>
+ <:col :let={{_id, link}} label="Shorten"><%= link.shorten %>
+ <:action :let={{_id, link}}>
+
+ <.link navigate={~p"/links/#{link}"}>Show
+
+ <.link patch={~p"/links/#{link}/edit"}>Edit
+
+ <:action :let={{id, link}}>
+ <.link
+ phx-click={JS.push("delete", value: %{id: link.id}) |> hide("##{id}")}
+ data-confirm="Are you sure?"
+ >
+ Delete
+
+
+
+
+<.modal :if={@live_action in [:new, :edit]} id="link-modal" show on_cancel={JS.patch(~p"/links")}>
+ <.live_component
+ module={LinkShortenerWeb.LinkLive.FormComponent}
+ id={@link.id || :new}
+ title={@page_title}
+ action={@live_action}
+ link={@link}
+ current_user={@current_user}
+ patch={~p"/links"}
+ />
+
diff --git a/lib/link_shortener_web/live/link_live/show.ex b/lib/link_shortener_web/live/link_live/show.ex
new file mode 100644
index 0000000..21ffd19
--- /dev/null
+++ b/lib/link_shortener_web/live/link_live/show.ex
@@ -0,0 +1,21 @@
+defmodule LinkShortenerWeb.LinkLive.Show do
+ use LinkShortenerWeb, :live_view
+
+ alias LinkShortener.Links
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok, socket}
+ end
+
+ @impl true
+ def handle_params(%{"id" => id}, _, socket) do
+ {:noreply,
+ socket
+ |> assign(:page_title, page_title(socket.assigns.live_action))
+ |> assign(:link, Links.get_one!(id))}
+ end
+
+ defp page_title(:show), do: "Show Link"
+ defp page_title(:edit), do: "Edit Link"
+end
diff --git a/lib/link_shortener_web/live/link_live/show.html.heex b/lib/link_shortener_web/live/link_live/show.html.heex
new file mode 100644
index 0000000..53fe938
--- /dev/null
+++ b/lib/link_shortener_web/live/link_live/show.html.heex
@@ -0,0 +1,25 @@
+<.header>
+ Link <%= @link.id %>
+ <:subtitle>This is a link record from your database.
+ <:actions>
+ <.link patch={~p"/links/#{@link}/show/edit"} phx-click={JS.push_focus()}>
+ <.button>Edit link
+
+
+
+
+<.list>
+
+
+<.back navigate={~p"/links"}>Back to links
+
+<.modal :if={@live_action == :edit} id="link-modal" show on_cancel={JS.patch(~p"/links/#{@link}")}>
+ <.live_component
+ module={LinkShortenerWeb.LinkLive.FormComponent}
+ id={@link.id}
+ title={@page_title}
+ action={@live_action}
+ link={@link}
+ patch={~p"/links/#{@link}"}
+ />
+
diff --git a/lib/link_shortener_web/router.ex b/lib/link_shortener_web/router.ex
index d845cde..8ad7d02 100644
--- a/lib/link_shortener_web/router.ex
+++ b/lib/link_shortener_web/router.ex
@@ -87,6 +87,13 @@ defmodule LinkShortenerWeb.Router do
on_mount: [{LinkShortenerWeb.UserAuth, :ensure_authenticated}] do
live "/users/settings", UserSettingsLive, :edit
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
+
+ live "/links", LinkLive.Index, :index
+ live "/links/new", LinkLive.Index, :new
+ live "/links/:id/edit", LinkLive.Index, :edit
+
+ live "/links/:id", LinkLive.Show, :show
+ live "/links/:id/show/edit", LinkLive.Show, :edit
end
end