berrypod/lib/berrypod_web/live/admin/pages/custom_form.ex
jamey ae6cf209aa complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
  primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
  semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
  order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
  chart dimensions) and one JS.toggle target

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00

186 lines
5.3 KiB
Elixir

defmodule BerrypodWeb.Admin.Pages.CustomForm do
use BerrypodWeb, :live_view
alias Berrypod.Pages
alias Berrypod.Pages.{Defaults, Page}
@impl true
def mount(params, _session, socket) do
{:ok, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(socket, :new, _params) do
changeset = Page.custom_changeset(%Page{}, %{})
socket
|> assign(:page_title, "New page")
|> assign(:page_struct, %Page{})
|> assign(:slug_touched, false)
|> assign(:selected_template, "blank")
|> assign(:form, to_form(changeset))
end
defp apply_action(socket, :edit, %{"slug" => slug}) do
case Pages.get_page_struct(slug) do
%Page{type: "custom"} = page ->
changeset = Page.custom_changeset(page, %{})
socket
|> assign(:page_title, "#{page.title} settings")
|> assign(:page_struct, page)
|> assign(:slug_touched, true)
|> assign(:form, to_form(changeset))
_ ->
socket
|> put_flash(:error, "Page not found")
|> push_navigate(to: ~p"/admin/pages")
end
end
@impl true
def handle_event("validate", %{"page" => params}, socket) do
params = maybe_slugify(params, socket)
form =
socket.assigns.page_struct
|> Page.custom_changeset(params)
|> Map.put(:action, :validate)
|> to_form()
slug_touched =
socket.assigns.slug_touched || (params["slug"] && params["slug"] != "")
{:noreply, assign(socket, form: form, slug_touched: slug_touched)}
end
@impl true
def handle_event("select_template", %{"key" => key}, socket) do
{:noreply, assign(socket, :selected_template, key)}
end
@impl true
def handle_event("save", %{"page" => params}, socket) do
save_page(socket, socket.assigns.live_action, params)
end
defp save_page(socket, :new, params) do
template_blocks = Defaults.template_blocks(socket.assigns.selected_template)
params = Map.put(params, "blocks", template_blocks)
case Pages.create_custom_page(params) do
{:ok, page} ->
{:noreply,
socket
|> put_flash(:info, "Page created")
|> push_navigate(to: ~p"/admin/pages/#{page.slug}")}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp save_page(socket, :edit, params) do
case Pages.update_custom_page(socket.assigns.page_struct, params) do
{:ok, page} ->
{:noreply,
socket
|> put_flash(:info, "Page settings saved")
|> push_navigate(to: ~p"/admin/pages/#{page.slug}")}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
# Auto-generate slug from title when creating and slug hasn't been manually edited
defp maybe_slugify(params, %{assigns: %{live_action: :new, slug_touched: false}}) do
case params["title"] do
title when is_binary(title) and title != "" ->
slug =
title
|> String.downcase()
|> String.replace(~r/[^a-z0-9\s-]/, "")
|> String.trim()
|> String.replace(~r/\s+/, "-")
|> String.replace(~r/-+/, "-")
|> String.trim("-")
Map.put(params, "slug", slug)
_ ->
params
end
end
defp maybe_slugify(params, _socket), do: params
@impl true
def render(assigns) do
~H"""
<.link navigate={~p"/admin/pages"} class="admin-back-link">
&larr; Pages
</.link>
<.header>
{if @live_action == :new, do: "New page", else: "Page settings"}
</.header>
<div :if={@live_action == :new} class="template-picker">
<p class="template-picker-label">Start from a template</p>
<div class="template-picker-cards">
<button
:for={tmpl <- Defaults.templates()}
type="button"
phx-click="select_template"
phx-value-key={tmpl.key}
class={[
"template-card",
assigns[:selected_template] == tmpl.key && "template-card-selected"
]}
>
<span class="template-card-name">{tmpl.label}</span>
<span class="template-card-desc">{tmpl.description}</span>
</button>
</div>
</div>
<.form
for={@form}
id="custom-page-form"
phx-change="validate"
phx-submit="save"
class="admin-form-stack admin-section"
>
<.input field={@form[:title]} label="Title" />
<.input field={@form[:slug]} label="URL slug" />
<.input
field={@form[:meta_description]}
type="textarea"
label="Meta description"
phx-no-feedback
/>
<.input field={@form[:published]} type="checkbox" label="Published" />
<.input field={@form[:show_in_nav]} type="checkbox" label="Show in navigation" />
<div
:if={@form[:show_in_nav].value == true}
class="admin-form-sub"
>
<.input field={@form[:nav_label]} label="Nav label" />
<.input field={@form[:nav_position]} type="number" label="Nav position" />
</div>
<div class="admin-row admin-row-lg">
<.button type="submit" phx-disable-with="Saving...">
{if @live_action == :new, do: "Create page", else: "Save settings"}
</.button>
<.link navigate={~p"/admin/pages"} class="admin-btn admin-btn-ghost">
Cancel
</.link>
</div>
</.form>
"""
end
end