berrypod/lib/berrypod_web/live/admin/pages/index.ex
jamey 045be2ed7e
All checks were successful
deploy / deploy (push) Successful in 1m21s
add admin CRUD for custom CMS pages
New settings form for creating and editing custom page metadata
(title, slug, meta description, published, nav settings). Pages
index shows custom pages section with draft badges and delete.
Editor shows settings button for custom pages, hides reset to
defaults. 20 new tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 09:43:03 +00:00

135 lines
4.6 KiB
Elixir

defmodule BerrypodWeb.Admin.Pages.Index do
use BerrypodWeb, :live_view
alias Berrypod.Pages
@page_groups [
{"Marketing", ~w(home about contact)},
{"Legal", ~w(delivery privacy terms)},
{"Shop", ~w(collection pdp cart search)},
{"Orders", ~w(checkout_success orders order_detail)},
{"System", ~w(error)}
]
@impl true
def mount(_params, _session, socket) do
pages = Pages.list_pages() |> Map.new(&{&1.slug, &1})
custom_pages = Pages.list_custom_pages()
{:ok,
socket
|> assign(:page_title, "Pages")
|> assign(:pages, pages)
|> assign(:custom_pages, custom_pages)
|> assign(:page_groups, @page_groups)}
end
@impl true
def handle_event("delete_custom_page", %{"slug" => slug}, socket) do
case Pages.get_page_struct(slug) do
%{type: "custom"} = page ->
{:ok, _} = Pages.delete_custom_page(page)
{:noreply,
socket
|> assign(:custom_pages, Pages.list_custom_pages())
|> put_flash(:info, "Page deleted")}
_ ->
{:noreply, put_flash(socket, :error, "Page not found")}
end
end
@impl true
def render(assigns) do
~H"""
<.header>
Pages
<:subtitle>Customise the layout and content of every page on your shop.</:subtitle>
<:actions>
<.link navigate={~p"/admin/pages/new"} class="admin-btn admin-btn-sm admin-btn-primary">
<.icon name="hero-plus" class="size-4" /> New page
</.link>
</:actions>
</.header>
<div class="page-list">
<div :for={{group_name, slugs} <- @page_groups} class="page-group">
<h3 class="page-group-title">{group_name}</h3>
<div class="page-group-cards">
<.link
:for={slug <- slugs}
navigate={~p"/admin/pages/#{slug}"}
class="page-card"
>
<span class="page-card-icon">
<.icon name={page_icon(slug)} class="size-5" />
</span>
<span class="page-card-info">
<span class="page-card-title">{@pages[slug].title}</span>
<span class="page-card-meta">
{length(@pages[slug].blocks)} {if length(@pages[slug].blocks) == 1,
do: "block",
else: "blocks"}
</span>
</span>
<.icon name="hero-chevron-right-mini" class="size-4 page-card-arrow" />
</.link>
</div>
</div>
<div :if={@custom_pages != []} class="page-group">
<h3 class="page-group-title">Custom pages</h3>
<div class="page-group-cards">
<div :for={page <- @custom_pages} class="page-card page-card-custom">
<.link navigate={~p"/admin/pages/#{page.slug}"} class="page-card-link">
<span class="page-card-icon">
<.icon name="hero-document" class="size-5" />
</span>
<span class="page-card-info">
<span class="page-card-title">
{page.title}
<span :if={!page.published} class="admin-badge admin-badge-warning ml-2">
Draft
</span>
</span>
<span class="page-card-meta">
/{page.slug} · {length(page.blocks)} {if length(page.blocks) == 1,
do: "block",
else: "blocks"}
</span>
</span>
<.icon name="hero-chevron-right-mini" class="size-4 page-card-arrow" />
</.link>
<button
phx-click="delete_custom_page"
phx-value-slug={page.slug}
data-confirm={"Delete \"#{page.title}\"? This cannot be undone."}
class="page-card-delete"
aria-label={"Delete #{page.title}"}
>
<.icon name="hero-trash" class="size-4" />
</button>
</div>
</div>
</div>
</div>
"""
end
defp page_icon("home"), do: "hero-home"
defp page_icon("about"), do: "hero-user"
defp page_icon("contact"), do: "hero-envelope"
defp page_icon("delivery"), do: "hero-truck"
defp page_icon("privacy"), do: "hero-shield-check"
defp page_icon("terms"), do: "hero-document-text"
defp page_icon("collection"), do: "hero-tag"
defp page_icon("pdp"), do: "hero-cube"
defp page_icon("cart"), do: "hero-shopping-cart"
defp page_icon("search"), do: "hero-magnifying-glass"
defp page_icon("checkout_success"), do: "hero-check-circle"
defp page_icon("orders"), do: "hero-clipboard-document-list"
defp page_icon("order_detail"), do: "hero-clipboard-document"
defp page_icon("error"), do: "hero-exclamation-triangle"
end