diff --git a/lib/decentralised_book_index_web/live/book_live/form_component.ex b/lib/decentralised_book_index_web/live/book_live/form_component.ex new file mode 100644 index 0000000..9d625ce --- /dev/null +++ b/lib/decentralised_book_index_web/live/book_live/form_component.ex @@ -0,0 +1,103 @@ +defmodule DecentralisedBookIndexWeb.BookLive.FormComponent do + use DecentralisedBookIndexWeb, :live_component + + @impl true + def render(assigns) do + ~H""" +
+ <.header> + {@title} + <:subtitle>Use this form to manage book records in your database. + + + <.simple_form + for={@form} + id="book-form" + phx-target={@myself} + phx-change="validate" + phx-submit="save" + > + <%= if @form.source.type == :create do %> + <.input field={@form[:bids]} type="select" multiple label="Bids" options={[]} /> + <.input + field={@form[:author_roles]} + type="select" + multiple + label="Author roles" + options={[]} + /> + <.input field={@form[:title]} type="text" label="Title" /><.input + field={@form[:description]} + type="text" + label="Description" + /><.input field={@form[:format]} type="text" label="Format" /><.input + field={@form[:language]} + type="text" + label="Language" + /><.input field={@form[:page_count]} type="number" label="Page count" /><.input + field={@form[:published]} + type="date" + label="Published" + /><.input field={@form[:publisher_id]} type="text" label="Publisher" /><.input + field={@form[:book_editions_registry_id]} + type="text" + label="Book editions registry" + /> + <% end %> + <%= if @form.source.type == :update do %> + <% end %> + + <:actions> + <.button phx-disable-with="Saving...">Save Book + + +
+ """ + end + + @impl true + def update(assigns, socket) do + {:ok, + socket + |> assign(assigns) + |> assign_form()} + end + + @impl true + def handle_event("validate", %{"book" => book_params}, socket) do + {:noreply, assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, book_params))} + end + + def handle_event("save", %{"book" => book_params}, socket) do + case AshPhoenix.Form.submit(socket.assigns.form, params: book_params) do + {:ok, book} -> + notify_parent({:saved, book}) + + socket = + socket + |> put_flash(:info, "Book #{socket.assigns.form.source.type}d successfully") + |> push_patch(to: socket.assigns.patch) + + {:noreply, socket} + + {:error, form} -> + {:noreply, assign(socket, form: form)} + end + end + + defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) + + defp assign_form(%{assigns: %{book: book}} = socket) do + form = + if book do + AshPhoenix.Form.for_update(book, :update, as: "book", actor: socket.assigns.current_user) + else + AshPhoenix.Form.for_create(DecentralisedBookIndex.Metadata.Book, :create, + as: "book", + actor: socket.assigns.current_user + ) + end + + assign(socket, form: to_form(form)) + end +end diff --git a/lib/decentralised_book_index_web/live/book_live/index.ex b/lib/decentralised_book_index_web/live/book_live/index.ex new file mode 100644 index 0000000..6e41a1e --- /dev/null +++ b/lib/decentralised_book_index_web/live/book_live/index.ex @@ -0,0 +1,118 @@ +defmodule DecentralisedBookIndexWeb.BookLive.Index do + use DecentralisedBookIndexWeb, :live_view + + @impl true + def render(assigns) do + ~H""" + <.header> + Listing Books + <:actions> + <.link patch={~p"/books/new"}> + <.button>New Book + + + + + <.table + id="books" + rows={@streams.books} + row_click={fn {_id, book} -> JS.navigate(~p"/books/#{book}") end} + > + <:col :let={{_id, book}} label="Id">{book.id} + + <:col :let={{_id, book}} label="Title">{book.title} + + <:col :let={{_id, book}} label="Description">{book.description} + + <:col :let={{_id, book}} label="Published">{book.published} + + <:col :let={{_id, book}} label="Language">{book.language} + + <:col :let={{_id, book}} label="Format">{book.format} + + <:col :let={{_id, book}} label="Page count">{book.page_count} + + <:col :let={{_id, book}} label="Cover image url">{book.cover_image_url} + + <:action :let={{_id, book}}> +
+ <.link navigate={~p"/books/#{book}"}>Show +
+ + <.link patch={~p"/books/#{book}/edit"}>Edit + + + <:action :let={{id, book}}> + <.link + phx-click={JS.push("delete", value: %{id: book.id}) |> hide("##{id}")} + data-confirm="Are you sure?" + > + Delete + + + + + <.modal :if={@live_action in [:new, :edit]} id="book-modal" show on_cancel={JS.patch(~p"/books")}> + <.live_component + module={DecentralisedBookIndexWeb.BookLive.FormComponent} + id={(@book && @book.id) || :new} + title={@page_title} + current_user={@current_user} + action={@live_action} + book={@book} + patch={~p"/books"} + /> + + """ + end + + @impl true + def mount(_params, _session, socket) do + {:ok, + socket + |> stream( + :books, + Ash.read!(DecentralisedBookIndex.Metadata.Book, actor: socket.assigns[:current_user]) + ) + |> assign_new(:current_user, fn -> nil end)} + 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 Book") + |> assign( + :book, + Ash.get!(DecentralisedBookIndex.Metadata.Book, id, actor: socket.assigns.current_user) + ) + end + + defp apply_action(socket, :new, _params) do + socket + |> assign(:page_title, "New Book") + |> assign(:book, nil) + end + + defp apply_action(socket, :index, _params) do + socket + |> assign(:page_title, "Listing Books") + |> assign(:book, nil) + end + + @impl true + def handle_info({DecentralisedBookIndexWeb.BookLive.FormComponent, {:saved, book}}, socket) do + {:noreply, stream_insert(socket, :books, book)} + end + + @impl true + def handle_event("delete", %{"id" => id}, socket) do + book = Ash.get!(DecentralisedBookIndex.Metadata.Book, id, actor: socket.assigns.current_user) + Ash.destroy!(book, actor: socket.assigns.current_user) + + {:noreply, stream_delete(socket, :books, book)} + end +end diff --git a/lib/decentralised_book_index_web/live/book_live/show.ex b/lib/decentralised_book_index_web/live/book_live/show.ex new file mode 100644 index 0000000..2d732c4 --- /dev/null +++ b/lib/decentralised_book_index_web/live/book_live/show.ex @@ -0,0 +1,70 @@ +defmodule DecentralisedBookIndexWeb.BookLive.Show do + use DecentralisedBookIndexWeb, :live_view + + @impl true + def render(assigns) do + ~H""" + <.header> + Book {@book.id} + <:subtitle>This is a book record from your database. + + <:actions> + <.link patch={~p"/books/#{@book}/show/edit"} phx-click={JS.push_focus()}> + <.button>Edit book + + + + + <.list> + <:item title="Id">{@book.id} + + <:item title="Title">{@book.title} + + <:item title="Description">{@book.description} + + <:item title="Published">{@book.published} + + <:item title="Language">{@book.language} + + <:item title="Format">{@book.format} + + <:item title="Page count">{@book.page_count} + + <:item title="Cover image url">{@book.cover_image_url} + + + <.back navigate={~p"/books"}>Back to books + + <.modal :if={@live_action == :edit} id="book-modal" show on_cancel={JS.patch(~p"/books/#{@book}")}> + <.live_component + module={DecentralisedBookIndexWeb.BookLive.FormComponent} + id={@book.id} + title={@page_title} + action={@live_action} + current_user={@current_user} + book={@book} + patch={~p"/books/#{@book}"} + /> + + """ + end + + @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( + :book, + Ash.get!(DecentralisedBookIndex.Metadata.Book, id, actor: socket.assigns.current_user) + )} + end + + defp page_title(:show), do: "Show Book" + defp page_title(:edit), do: "Edit Book" +end diff --git a/lib/decentralised_book_index_web/router.ex b/lib/decentralised_book_index_web/router.ex index dcd79c8..4946d31 100644 --- a/lib/decentralised_book_index_web/router.ex +++ b/lib/decentralised_book_index_web/router.ex @@ -35,6 +35,13 @@ defmodule DecentralisedBookIndexWeb.Router do scope "/", DecentralisedBookIndexWeb do pipe_through :browser + live "/books", BookLive.Index, :index + live "/books/new", BookLive.Index, :new + live "/books/:id/edit", BookLive.Index, :edit + + live "/books/:id", BookLive.Show, :show + live "/books/:id/show/edit", BookLive.Show, :edit + live "/authors", AuthorLive.Index, :index live "/authors/new", AuthorLive.Index, :new live "/authors/:id/edit", AuthorLive.Index, :edit