diff --git a/lib/beacon/live_admin/content.ex b/lib/beacon/live_admin/content.ex
index 0bb8e662..4e518102 100644
--- a/lib/beacon/live_admin/content.ex
+++ b/lib/beacon/live_admin/content.ex
@@ -162,4 +162,28 @@ defmodule Beacon.LiveAdmin.Content do
def update_component(site, component, attrs) do
call(site, Beacon.Content, :update_component, [component, attrs])
end
+
+ def change_error_page(site, error_page, attrs \\ %{}) do
+ call(site, Beacon.Content, :change_error_page, [error_page, attrs])
+ end
+
+ def create_error_page(site, attrs) do
+ call(site, Beacon.Content, :create_error_page, [attrs])
+ end
+
+ def list_error_pages(site) do
+ call(site, Beacon.Content, :list_error_pages, [site])
+ end
+
+ def update_error_page(site, error_page, attrs) do
+ call(site, Beacon.Content, :update_error_page, [error_page, attrs])
+ end
+
+ def delete_error_page(site, error_page) do
+ call(site, Beacon.Content, :delete_error_page, [error_page])
+ end
+
+ def valid_error_statuses(site) do
+ call(site, Beacon.Content.ErrorPage, :valid_statuses, [])
+ end
end
diff --git a/lib/beacon/live_admin/live/error_page_editor_live/index.ex b/lib/beacon/live_admin/live/error_page_editor_live/index.ex
new file mode 100644
index 00000000..6afeb76a
--- /dev/null
+++ b/lib/beacon/live_admin/live/error_page_editor_live/index.ex
@@ -0,0 +1,276 @@
+defmodule Beacon.LiveAdmin.ErrorPageEditorLive.Index do
+ @moduledoc false
+ use Beacon.LiveAdmin.PageBuilder
+
+ alias Beacon.LiveAdmin.Content
+
+ on_mount({Beacon.LiveAdmin.Hooks.Authorized, {:error_pages, :index}})
+
+ def menu_link(_, :index), do: {:root, "Error Pages"}
+
+ def handle_params(params, _uri, socket) do
+ %{beacon_page: %{site: site}} = socket.assigns
+
+ socket =
+ socket
+ |> assign(page_title: "Error Pages")
+ |> assign(unsaved_changes: false)
+ |> assign(show_create_modal: false)
+ |> assign(show_nav_modal: false)
+ |> assign(show_delete_modal: false)
+ |> assign(show_status_change_field: false)
+ |> assign(create_form: to_form(%{}, as: :error_page))
+ |> assign_new(:error_pages, fn -> Content.list_error_pages(site) end)
+ |> assign_new(:layouts, fn -> Content.list_layouts(site) end)
+ |> assign_selected(params["status"])
+ |> assign_form()
+
+ {:noreply, socket}
+ end
+
+ def handle_event("select-" <> status, _, socket) do
+ %{beacon_page: %{site: site}} = socket.assigns
+
+ path = beacon_live_admin_path(socket, site, "/error_pages/#{status}")
+
+ if socket.assigns.unsaved_changes do
+ {:noreply, assign(socket, show_nav_modal: true, confirm_nav_path: path)}
+ else
+ {:noreply, push_redirect(socket, to: path)}
+ end
+ end
+
+ def handle_event("error_page_template_editor_lost_focus", %{"value" => template}, socket) do
+ %{selected: selected, beacon_page: %{site: site}, form: form} = socket.assigns
+
+ changeset =
+ site
+ |> Content.change_error_page(selected, %{
+ "site" => site,
+ "template" => template,
+ "status" => form.params["status"] || Map.fetch!(form.data, :status),
+ "layout_id" => form.params["layout_id"] || Map.fetch!(form.data, :layout_id)
+ })
+ |> Map.put(:action, :validate)
+
+ socket =
+ socket
+ |> assign(form: to_form(changeset))
+ |> assign(changed_template: template)
+ |> assign(unsaved_changes: !(changeset.changes == %{}))
+
+ {:noreply, socket}
+ end
+
+ def handle_event("create_new", _, socket) do
+ {:noreply, assign(socket, show_create_modal: true)}
+ end
+
+ def handle_event("change_status", _, socket) do
+ {:noreply, assign(socket, show_status_change_field: true)}
+ end
+
+ def handle_event("save_new", %{"status" => status}, socket) do
+ %{beacon_page: %{site: site}, layouts: layouts} = socket.assigns
+
+ attrs = %{
+ "status" => status,
+ "site" => site,
+ "template" => "Something went wrong",
+ "layout_id" => Enum.find(layouts, &(&1.title == "Default")).id
+ }
+
+ socket =
+ case Content.create_error_page(site, attrs) do
+ {:ok, _} ->
+ socket
+ |> assign(error_pages: Content.list_error_pages(site))
+ |> assign_selected(status)
+ |> assign(show_create_modal: false)
+ |> push_redirect(to: beacon_live_admin_path(socket, site, "/error_pages/#{status}"))
+
+ {:error, changeset} ->
+ assign(socket, create_form: to_form(changeset))
+ end
+
+ {:noreply, socket}
+ end
+
+ def handle_event("save_changes", %{"error_page" => params}, socket) do
+ %{selected: selected, beacon_page: %{site: site}} = socket.assigns
+
+ attrs = %{layout_id: params["layout_id"], template: params["template"]}
+
+ socket =
+ case Content.update_error_page(site, selected, attrs) do
+ {:ok, updated_error_page} ->
+ socket
+ |> assign_error_page_update(updated_error_page)
+ |> assign_selected(selected.status)
+ |> assign_form()
+ |> assign(unsaved_changes: false)
+ |> put_flash(:info, "Error page updated successfully")
+
+ {:error, changeset} ->
+ changeset = Map.put(changeset, :action, :update)
+ assign(socket, form: to_form(changeset))
+ end
+
+ {:noreply, socket}
+ end
+
+ def handle_event("delete", _, socket) do
+ {:noreply, assign(socket, show_delete_modal: true)}
+ end
+
+ def handle_event("delete_confirm", _, socket) do
+ %{selected: error_page, beacon_page: %{site: site}} = socket.assigns
+
+ {:ok, _} = Content.delete_error_page(site, error_page)
+
+ socket =
+ socket
+ |> assign(error_pages: Content.list_error_pages(site))
+ |> push_patch(to: beacon_live_admin_path(socket, site, "/error_pages"))
+
+ {:noreply, socket}
+ end
+
+ def handle_event("delete_cancel", _, socket) do
+ {:noreply, assign(socket, show_delete_modal: false)}
+ end
+
+ def handle_event("stay_here", _params, socket) do
+ {:noreply, assign(socket, show_nav_modal: false, confirm_nav_path: nil)}
+ end
+
+ def handle_event("discard_changes", _params, socket) do
+ {:noreply, push_redirect(socket, to: socket.assigns.confirm_nav_path)}
+ end
+
+ def handle_event("cancel_create", _params, socket) do
+ {:noreply, assign(socket, show_create_modal: false)}
+ end
+
+ defp assign_selected(socket, nil) do
+ case socket.assigns.error_pages do
+ [] -> assign(socket, selected: nil, changed_template: "")
+ [hd | _] -> assign(socket, selected: hd, changed_template: hd.template)
+ end
+ end
+
+ defp assign_selected(socket, status) when is_binary(status) do
+ assign_selected(socket, String.to_integer(status))
+ end
+
+ defp assign_selected(socket, status) when is_integer(status) do
+ selected = Enum.find(socket.assigns.error_pages, &(&1.status == status))
+ assign(socket, selected: selected, changed_template: selected.template)
+ end
+
+ defp assign_form(socket) do
+ form =
+ case socket.assigns do
+ %{selected: nil} ->
+ nil
+
+ %{selected: selected, beacon_page: %{site: site}} ->
+ site
+ |> Content.change_error_page(selected)
+ |> to_form()
+ end
+
+ assign(socket, form: form)
+ end
+
+ defp assign_error_page_update(socket, updated_error_page) do
+ %{id: error_page_id} = updated_error_page
+
+ error_pages =
+ Enum.map(socket.assigns.error_pages, fn
+ %{id: ^error_page_id} -> updated_error_page
+ other -> other
+ end)
+
+ assign(socket, error_pages: error_pages)
+ end
+
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ <%= @page_title %>
+ <:actions>
+ <.button type="button" id="new-error-page-button" phx-click="create_new" class="uppercase">
+ New Error Page
+
+
+
+
+ <.main_content class="h-[calc(100vh_-_223px)]">
+ <.modal :if={@show_nav_modal} id="confirm-nav" on_cancel={JS.push("stay_here")} show>
+
You've made unsaved changes to this error page!
+
Navigating to another error page without saving will cause these changes to be lost.
+ <.button type="button" phx-click="stay_here">
+ Stay here
+
+ <.button type="button" phx-click="discard_changes">
+ Discard changes
+
+
+
+ <.modal :if={@show_create_modal} id="create-modal" on_cancel={JS.push("cancel_create")} show>
+ <.simple_form :let={f} for={@create_form} id="create-form" phx-submit="save_new">
+ <.input field={f[:status]} type="select" label="Status code for new error page:" options={Content.valid_error_statuses(@beacon_page.site)} />
+ <:actions>
+ <.button>Save
+
+
+
+
+ <.modal :if={@show_delete_modal} id="delete-modal" on_cancel={JS.push("delete_cancel")} show>
+
Are you sure you want to delete this error page?
+ <.button type="button" id="confirm-delete-button" phx-click="delete_confirm">
+ Delete
+
+ <.button type="button" phx-click="delete_cancel">
+ Cancel
+
+
+
+
+
+ <.table id="error-pages" rows={@error_pages} row_click={fn row -> "select-#{row.status}" end}>
+ <:col :let={error_page} label="status">
+ <%= Map.fetch!(error_page, :status) %>
+
+
+
+
+
+ <.form :let={f} for={@form} id="error-page-form" class="flex items-end gap-4" phx-submit="save_changes">
+ <.input label="Status" field={f[:status]} type="text" disabled readonly />
+ <.input label="Layout" field={f[:layout_id]} options={Enum.map(@layouts, &{&1.title, &1.id})} value={@selected.layout_id} type="select" />
+ <.input type="hidden" field={f[:template]} name="error_page[template]" id="error_page-form_template" value={@changed_template} />
+
+ <.button phx-disable-with="Saving..." class="ml-auto">Save Changes
+ <.button id="delete-error-page-button" type="button" phx-click="delete" class="">Delete
+
+
+
+
+
+
+
+ """
+ end
+end
diff --git a/lib/beacon/live_admin/live/home_live.ex b/lib/beacon/live_admin/live/home_live.ex
index eda24cac..8aefe83e 100644
--- a/lib/beacon/live_admin/live/home_live.ex
+++ b/lib/beacon/live_admin/live/home_live.ex
@@ -41,6 +41,13 @@ defmodule Beacon.LiveAdmin.HomeLive do
Layouts
+ <.link
+ href={Beacon.LiveAdmin.Router.beacon_live_admin_path(@socket, site, "/components")}
+ class="whitespace-nowrap text-sm leading-5 py-3.5 font-bold tracking-widest text-center uppercase bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-blue-200 active:bg-blue-800 px-6 text-gray-50"
+ >
+ Components
+
+
<.link
href={Beacon.LiveAdmin.Router.beacon_live_admin_path(@socket, site, "/pages")}
class="whitespace-nowrap text-sm leading-5 py-3.5 font-bold tracking-widest text-center uppercase bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-blue-200 active:bg-blue-800 px-6 text-gray-50"
@@ -49,11 +56,12 @@ defmodule Beacon.LiveAdmin.HomeLive do
<.link
- href={Beacon.LiveAdmin.Router.beacon_live_admin_path(@socket, site, "/components")}
- class="whitespace-nowrap text-sm leading-5 py-3.5 font-bold tracking-widest text-center uppercase bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-blue-200 active:bg-blue-800 px-6 text-gray-50"
+ href={Beacon.LiveAdmin.Router.beacon_live_admin_path(@socket, site, "/error_pages")}
+ class="whitespace-nowrap text-sm leading-5 py-3.5 font-bold tracking-widest text-center uppercase bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-blue-200 active:bg-blue-800 px-6 text-gray-50"
>
- Components
+ Error Pages
+
<.link
href={Beacon.LiveAdmin.Router.beacon_live_admin_path(@socket, site, "/media_library")}
class="whitespace-nowrap text-sm leading-5 py-3.5 font-bold tracking-widest text-center uppercase bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-blue-200 active:bg-blue-800 px-6 text-gray-50"
diff --git a/lib/beacon/live_admin/page_live.ex b/lib/beacon/live_admin/page_live.ex
index 549ab39c..2e1c0256 100644
--- a/lib/beacon/live_admin/page_live.ex
+++ b/lib/beacon/live_admin/page_live.ex
@@ -148,10 +148,12 @@ defmodule Beacon.LiveAdmin.PageLive do
case {a, b} do
{"/layouts", _} -> true
{_, "/layouts"} -> false
- {"/pages", _} -> true
- {_, "/pages"} -> false
{"/components", _} -> true
{_, "/components"} -> false
+ {"/pages", _} -> true
+ {_, "/pages"} -> false
+ {"/error_pages", _} -> true
+ {_, "/error_pages"} -> false
{"/media_library", _} -> true
{_, "/media_library"} -> false
{a, b} -> a <= b
diff --git a/lib/beacon/live_admin/router.ex b/lib/beacon/live_admin/router.ex
index b01fb021..4c156e45 100644
--- a/lib/beacon/live_admin/router.ex
+++ b/lib/beacon/live_admin/router.ex
@@ -122,6 +122,7 @@ defmodule Beacon.LiveAdmin.Router do
end)
[
+ # layouts
{"/layouts", Beacon.LiveAdmin.LayoutEditorLive.Index, :index, %{}},
{"/layouts/new", Beacon.LiveAdmin.LayoutEditorLive.New, :new, %{}},
{"/layouts/:id", Beacon.LiveAdmin.LayoutEditorLive.Edit, :edit, %{}},
@@ -129,6 +130,11 @@ defmodule Beacon.LiveAdmin.Router do
{"/layouts/:id/revisions", Beacon.LiveAdmin.LayoutEditorLive.Revisions, :revisions, %{}},
{"/layouts/:id/resource_links", Beacon.LiveAdmin.LayoutEditorLive.ResourceLinks,
:resource_links, %{}},
+ # components
+ {"/components", Beacon.LiveAdmin.ComponentEditorLive.Index, :index, %{}},
+ {"/components/new", Beacon.LiveAdmin.ComponentEditorLive.New, :new, %{}},
+ {"/components/:id", Beacon.LiveAdmin.ComponentEditorLive.Edit, :edit, %{}},
+ # pages
{"/pages", Beacon.LiveAdmin.PageEditorLive.Index, :index, %{}},
{"/pages/new", Beacon.LiveAdmin.PageEditorLive.New, :new, %{}},
{"/pages/:id", Beacon.LiveAdmin.PageEditorLive.Edit, :edit, %{}},
@@ -141,9 +147,10 @@ defmodule Beacon.LiveAdmin.Router do
{"/pages/:page_id/variants", Beacon.LiveAdmin.PageEditorLive.Variants, :variants, %{}},
{"/pages/:page_id/variants/:variant_id", Beacon.LiveAdmin.PageEditorLive.Variants,
:variants, %{}},
- {"/components", Beacon.LiveAdmin.ComponentEditorLive.Index, :index, %{}},
- {"/components/new", Beacon.LiveAdmin.ComponentEditorLive.New, :new, %{}},
- {"/components/:id", Beacon.LiveAdmin.ComponentEditorLive.Edit, :edit, %{}},
+ # error pages
+ {"/error_pages", Beacon.LiveAdmin.ErrorPageEditorLive.Index, :index, %{}},
+ {"/error_pages/:status", Beacon.LiveAdmin.ErrorPageEditorLive.Index, :index, %{}},
+ # media library
{"/media_library", Beacon.LiveAdmin.MediaLibraryLive.Index, :index, %{}},
{"/media_library/upload", Beacon.LiveAdmin.MediaLibraryLive.Index, :upload, %{}},
{"/media_library/:id", Beacon.LiveAdmin.MediaLibraryLive.Index, :show, %{}}
diff --git a/mix.lock b/mix.lock
index c0ef99fe..6230b825 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,7 +1,6 @@
%{
"accent": {:hex, :accent, "1.1.1", "20257356446d45078b19b91608f74669b407b39af891ee3db9ee6824d1cae19d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "6d5afa50d4886e3370e04fa501468cbaa6c4b5fe926f72ccfa844ad9e259adae"},
- "beacon": {:git, "https://github.com/beaconCMS/beacon.git", "884fbf717eefc0c9fb51cbc795410e453fa50e17", []},
- "brotli": {:hex, :brotli, "0.3.2", "59cf45a399098516f1d34f70d8e010e5c9bf326659d3ef34c7cc56793339002b", [:rebar3], [], "hexpm", "9ec3ef9c753f80d0c657b4905193c55e5198f169fa1d1c044d8601d4d931a2ad"},
+ "beacon": {:git, "https://github.com/beaconCMS/beacon.git", "f5681121068b642a0e9cd691bce94fd1e7549c6d", []},
"castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.7", "77de20ac77f0e53f20ca82c563520af0237c301a1ec3ab3bc598e8a96c7ee5d9", [:mix], [{:elixir_make, "~> 0.7.3", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2768b28bf3c2b4f788c995576b39b8cb5d47eb788526d93bd52206c1d8bf4b75"},
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
@@ -17,6 +16,7 @@
"esbuild": {:hex, :esbuild, "0.7.1", "fa0947e8c3c3c2f86c9bf7e791a0a385007ccd42b86885e8e893bdb6631f5169", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "66661cdf70b1378ee4dc16573fcee67750b59761b2605a0207c267ab9d19f13c"},
"ex_aws": {:hex, :ex_aws, "2.4.3", "6c6d88ba7b9c07e3b0f4b70406d5fccb9f5358f5ef18138f7bd396f7863e8255", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "67f61f8b6aec740150d483a21f551fabce26a481d9917305ed2bb47717007519"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"},
+ "ex_brotli": {:hex, :ex_brotli, "0.3.0", "69d5f3720df70d5c89d1395d8fbe49ba37466b626834aaf6d77c72e0c93cf975", [:mix], [{:phoenix, ">= 0.0.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:rustler, "~> 0.29", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "8e46982f7d20069419ca8c8c54f9f3ebd9fa0e1d094c54cbf8ce3d636d84dfa7"},
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"floki": {:hex, :floki, "0.34.3", "5e2dcaec5d7c228ce5b1d3501502e308b2d79eb655e4191751a1fe491c37feac", [:mix], [], "hexpm", "9577440eea5b97924b4bf3c7ea55f7b8b6dce589f9b28b096cc294a8dc342341"},
@@ -29,16 +29,16 @@
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"},
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
- "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"},
+ "phoenix": {:hex, :phoenix, "1.7.9", "9a2b873e2cb3955efdd18ad050f1818af097fa3f5fc3a6aaba666da36bdd3f02", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "83e32da028272b4bfd076c61a964e6d2b9d988378df2f1276a0ed21b13b5e997"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"},
- "phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"},
+ "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
- "phoenix_live_view": {:hex, :phoenix_live_view, "0.19.5", "6e730595e8e9b8c5da230a814e557768828fd8dfeeb90377d2d8dbb52d4ec00a", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2eaa0dd3cfb9bd7fb949b88217df9f25aed915e986a28ad5c8a0d054e7ca9d3"},
+ "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.1", "92a37acf07afca67ac98bd326532ba8f44ad7d4bdf3e4361b03f7f02594e5ae9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be494fd1215052729298b0e97d5c2ce8e719c00854b82cd8cf15c1cd7fcf6294"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"},
- "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
+ "plug": {:hex, :plug, "1.15.1", "b7efd81c1a1286f13efb3f769de343236bd8b7d23b4a9f40d3002fc39ad8f74c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "459497bd94d041d98d948054ec6c0b76feacd28eec38b219ca04c0de13c79d30"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
- "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
+ "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
"postgrex": {:hex, :postgrex, "0.17.1", "01c29fd1205940ee55f7addb8f1dc25618ca63a8817e56fac4f6846fc2cddcbe", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "14b057b488e73be2beee508fb1955d8db90d6485c6466428fe9ccf1d6692a555"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"rustler_precompiled": {:hex, :rustler_precompiled, "0.7.0", "5d0834fc06dbc76dd1034482f17b1797df0dba9b491cef8bb045fcaca94bcade", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "fdf43a6835f4e4de5bfbc4c019bfb8c46d124bd4635fefa3e20d9a2bbbec1512"},
diff --git a/test/beacon/live_admin/live/error_page_editor_live/index_test.exs b/test/beacon/live_admin/live/error_page_editor_live/index_test.exs
new file mode 100644
index 00000000..9fa431bb
--- /dev/null
+++ b/test/beacon/live_admin/live/error_page_editor_live/index_test.exs
@@ -0,0 +1,79 @@
+defmodule Beacon.LiveAdmin.ErrorPageEditorLive.IndexTest do
+ use Beacon.LiveAdmin.ConnCase, async: false
+
+ import Beacon.LiveAdminTest.Cluster, only: [rpc: 4]
+
+ setup do
+ default_layout = layout_fixture(node1(), %{title: "Default"})
+ another_layout = layout_fixture(node1(), %{title: "Another"})
+ attrs = %{status: 404, layout_id: default_layout.id, template: "Not Found"}
+ error_page_fixture(node1(), attrs)
+ attrs = %{status: 500, layout_id: default_layout.id, template: "Internal Server Error"}
+ error_page_fixture(node1(), attrs)
+
+ on_exit(fn ->
+ rpc(node1(), Beacon.Repo, :delete_all, [Beacon.Content.ErrorPage, [log: false]])
+ rpc(node1(), Beacon.Repo, :delete_all, [Beacon.Content.Layout, [log: false]])
+ end)
+
+ [another_layout: another_layout]
+ end
+
+ test "select error page via path", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/admin/site_a/error_pages")
+ assert has_element?(view, "input[name='error_page[status]'][value=404]")
+
+ {:ok, view, _html} = live(conn, "/admin/site_a/error_pages/404")
+ assert has_element?(view, "input[name='error_page[status]'][value=404]")
+
+ {:ok, view, _html} = live(conn, "/admin/site_a/error_pages/500")
+ assert has_element?(view, "input[name='error_page[status]'][value=500]")
+ end
+
+ test "create a new error page", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/admin/site_a/error_pages")
+
+ view |> element("#new-error-page-button") |> render_click()
+
+ assert has_element?(view, "#create-modal")
+
+ {:ok, view, _html} =
+ view
+ |> form("#create-form", %{status: 400})
+ |> render_submit()
+ |> follow_redirect(conn, "/admin/site_a/error_pages/400")
+
+ refute has_element?(view, "#create-modal")
+ assert has_element?(view, "input[name='error_page[status]'][value=400]")
+ end
+
+ test "update an error page", %{conn: conn, another_layout: layout} do
+ {:ok, view, html} = live(conn, "/admin/site_a/error_pages/404")
+
+ assert has_element?(view, "[selected=\"selected\"]", "Default")
+
+ view
+ |> form("#error-page-form", error_page: %{layout_id: layout.id})
+ |> render_submit()
+
+ assert has_element?(view, "p", "Error page updated successfully")
+
+ refute has_element?(view, "[selected=\"selected\"]", "Default")
+ assert has_element?(view, "[selected=\"selected\"]", "Another")
+ end
+
+ test "delete error page", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/admin/site_a/error_pages/500")
+
+ assert has_element?(view, "span", "500")
+
+ view |> element("#delete-error-page-button") |> render_click()
+
+ assert has_element?(view, "#delete-modal")
+
+ view |> element("#confirm-delete-button") |> render_click()
+
+ refute has_element?(view, "#delete-modal")
+ refute has_element?(view, "span", "500")
+ end
+end
diff --git a/test/support/fixtures.ex b/test/support/fixtures.ex
index 4e62708e..2acec53d 100644
--- a/test/support/fixtures.ex
+++ b/test/support/fixtures.ex
@@ -42,6 +42,20 @@ defmodule Beacon.LiveAdmin.Fixtures do
rpc(node, Beacon.Content, :create_page!, [attrs])
end
+ def error_page_fixture(node \\ node1(), attrs \\ %{}) do
+ layout_id = get_lazy(attrs, :layout_id, fn -> layout_fixture().id end)
+
+ attrs =
+ Enum.into(attrs, %{
+ site: "site_a",
+ status: Enum.random(111..999),
+ layout_id: layout_id,
+ template: "Oops"
+ })
+
+ rpc(node, Beacon.Content, :create_error_page!, [attrs])
+ end
+
def media_library_asset_fixture(node \\ node1(), attrs \\ %{}) do
file_metadata = file_metadata_fixture(node, attrs)
rpc(node, Beacon.MediaLibrary, :upload, [file_metadata])