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 @@
- + Links
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