From dd7146cb41eb27893cfc40da083a808e8df13682 Mon Sep 17 00:00:00 2001 From: jamey Date: Sat, 28 Mar 2026 23:54:04 +0000 Subject: [PATCH] merge page settings into Page tab and remove Settings tab Custom page settings (title, slug, meta, published, navigation) now appear as a collapsible section at the top of the Page tab. The separate Settings tab has been fully removed. - Add page_settings_section component to page_renderer.ex - Remove dead :settings case from editor_panel_content - Delete settings_editor.ex component entirely - Remove SettingsEditor import from shop_components.ex - Add .page-settings-* CSS styles - Clean up old .settings-* CSS styles Co-Authored-By: Claude Opus 4.5 --- PROGRESS.md | 4 +- assets/css/admin/components.css | 159 ++++---- docs/plans/editor-reorganisation.md | 23 +- .../components/shop_components.ex | 2 - .../shop_components/settings_editor.ex | 347 ------------------ lib/berrypod_web/page_renderer.ex | 171 +++++++-- 6 files changed, 267 insertions(+), 439 deletions(-) delete mode 100644 lib/berrypod_web/components/shop_components/settings_editor.ex diff --git a/PROGRESS.md b/PROGRESS.md index 6aff579..2e5f840 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -161,9 +161,11 @@ Restructure the 3-tab editor panel for better discoverability. Replace Settings | 10-14 | Header & footer navigation editors | 3h | done | | 15-16 | Footer content (about, copyright, newsletter toggle) | 1.25h | done | | 17-18 | Move branding from Theme to Site | 1.5h | done | -| 19-20 | Merge page settings into Page tab, remove Settings tab | 1h | planned | +| 19-20 | Merge page settings into Page tab, remove Settings tab | 1h | done | | 21-22 | Polish and testing | 2h | planned | +Custom page settings (title, slug, meta description, published, navigation options) now appear inline in the Page tab as a collapsible section. The separate Settings tab has been fully removed along with the SettingsEditor component. + Social links now support 40+ platforms (grouped by category), auto-detect platform from pasted URLs (including deep links like `tg://` and `spotify:`), normalize bare domains to https://, preserve custom protocols, and filter empty URLs from shop display. Navigation editors support add/edit/delete/reorder for both header and footer nav items. Page picker dropdown allows linking to system pages (home, about, contact, etc.) or custom published pages. Changes preview immediately with live update in the shop layout. Removed legacy /admin/navigation page — nav editing is now exclusively through the Site tab with live preview. diff --git a/assets/css/admin/components.css b/assets/css/admin/components.css index 99d8e01..3bba64e 100644 --- a/assets/css/admin/components.css +++ b/assets/css/admin/components.css @@ -2440,6 +2440,99 @@ } } +/* Page settings section (inline in Page tab for custom pages) */ + +.page-settings-details { + margin-bottom: 0.75rem; + border: 1px solid var(--admin-border); + border-radius: var(--admin-radius); + background: var(--admin-bg-alt); +} + +.page-settings-summary { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.625rem 0.75rem; + cursor: pointer; + user-select: none; + font-size: 0.8125rem; + font-weight: 500; + color: var(--admin-text-secondary); + + &:hover { + color: var(--admin-text-primary); + } + + & .page-settings-chevron { + margin-left: auto; + transition: transform 0.15s ease; + } +} + +.page-settings-details[open] .page-settings-chevron { + transform: rotate(180deg); +} + +.page-settings-form { + display: flex; + flex-direction: column; + gap: 0.75rem; + padding: 0 0.75rem 0.75rem; +} + +.page-settings-field { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.page-settings-label { + font-size: 0.75rem; + font-weight: 500; + color: var(--admin-text-secondary); +} + +.page-settings-slug-input { + display: flex; + align-items: center; + gap: 0; +} + +.page-settings-slug-prefix { + padding: 0.375rem 0.5rem; + background: var(--admin-bg); + border: 1px solid var(--admin-border); + border-right: none; + border-radius: var(--admin-radius) 0 0 var(--admin-radius); + font-size: 0.875rem; + color: var(--admin-text-tertiary); +} + +.page-settings-slug-input .admin-input { + border-radius: 0 var(--admin-radius) var(--admin-radius) 0; +} + +.page-settings-checks { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +.page-settings-nav-options { + display: flex; + gap: 0.75rem; + padding-left: 1.5rem; +} + +.page-settings-field-inline { + flex: 1; +} + +.page-settings-field-sm { + width: 5rem; +} + /* Block list in editor */ .block-list { @@ -5133,72 +5226,6 @@ /* Last group in a customise section (no border, tighter margin than theme-group) */ .theme-group-flush { margin-bottom: 1rem; } -/* ── Settings editor (on-site editor Settings tab) ── */ - -.settings-editor-form { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.settings-slug-preview { - display: flex; - align-items: center; - gap: 0.25rem; -} - -.settings-slug-preview .admin-input { - flex: 1; -} - -.settings-nav-options { - padding-left: 1rem; - border-left: 2px solid var(--admin-border); -} - -.settings-nav-row { - display: flex; - gap: 0.75rem; -} - -.settings-nav-field { - flex: 1; -} - -.settings-nav-field-sm { - flex: 0 0 5rem; -} - -.settings-actions { - display: flex; - align-items: center; - gap: 0.75rem; - padding-top: 0.5rem; -} - -.settings-save-indicator { - display: flex; - align-items: center; - gap: 0.25rem; - font-size: 0.875rem; - color: var(--admin-success); -} - -.settings-save-error { - color: var(--admin-error); -} - -.settings-admin-link { - padding-top: 0.5rem; - border-top: 1px solid var(--admin-border); -} - -.settings-info-view { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - /* ── Content width containers ── */ .admin-content-narrow { max-width: 32rem; } diff --git a/docs/plans/editor-reorganisation.md b/docs/plans/editor-reorganisation.md index 59c582b..abcce2c 100644 --- a/docs/plans/editor-reorganisation.md +++ b/docs/plans/editor-reorganisation.md @@ -215,8 +215,8 @@ end | 16 | Footer: read new fields | 15 | 30m | done | | 17 | Move branding settings from Theme to Site | 4 | 1h | done | | 18 | Theme tab: remove branding, polish remaining | 17 | 30m | done | -| 19 | Merge page settings into Page tab | — | 45m | planned | -| 20 | Remove Settings tab | 19 | 15m | planned | +| 19 | Merge page settings into Page tab | — | 45m | done | +| 20 | Remove Settings tab | 19 | 15m | done | | 21 | Polish: responsive, empty states, validation | 1-20 | 1.5h | planned | | 22 | Testing: all page types, edge cases | 21 | 30m | planned | @@ -328,6 +328,25 @@ Moved branding settings from Theme tab to Site tab. - Accent, hover, sale colours - Customise accordion with typography, colours, layout, shape, products, product page groups +### Completed: Page Settings Migration (Tasks 19-20) + +Merged custom page settings inline into Page tab and removed Settings tab. + +**What changed:** +- Custom pages show a collapsible "Page settings" section at the top of the Page tab +- Contains: title, URL slug, meta description, published toggle, show in nav toggle, nav label, nav position +- System pages, product pages, and collection pages no longer show settings (editing done elsewhere) +- Settings tab button was already removed from UI; dead code removed from page_renderer.ex +- Deleted `settings_editor.ex` component entirely + +**Files modified:** +- `lib/berrypod_web/page_renderer.ex` - Added `page_settings_section` component inline in `editor_sheet_content` +- `lib/berrypod_web/components/shop_components.ex` - Removed SettingsEditor import +- `assets/css/admin/components.css` - Added `.page-settings-*` styles, removed old `.settings-*` styles + +**Files deleted:** +- `lib/berrypod_web/components/shop_components/settings_editor.ex` + ## UI Wireframes ### Site tab layout diff --git a/lib/berrypod_web/components/shop_components.ex b/lib/berrypod_web/components/shop_components.ex index f9fa0d2..4215125 100644 --- a/lib/berrypod_web/components/shop_components.ex +++ b/lib/berrypod_web/components/shop_components.ex @@ -10,7 +10,6 @@ defmodule BerrypodWeb.ShopComponents do - `Product` — product cards, gallery, variant selector, hero sections - `Content` — rich text, responsive images, contact form, reviews - `ThemeEditor` — shared theme editor components for admin and on-site editing - - `SettingsEditor` — shared settings editor components for on-site editing """ defmacro __using__(_opts \\ []) do @@ -20,7 +19,6 @@ defmodule BerrypodWeb.ShopComponents do import BerrypodWeb.ShopComponents.Content import BerrypodWeb.ShopComponents.Layout import BerrypodWeb.ShopComponents.Product - import BerrypodWeb.ShopComponents.SettingsEditor import BerrypodWeb.ShopComponents.ThemeEditor end end diff --git a/lib/berrypod_web/components/shop_components/settings_editor.ex b/lib/berrypod_web/components/shop_components/settings_editor.ex deleted file mode 100644 index 6b67b2a..0000000 --- a/lib/berrypod_web/components/shop_components/settings_editor.ex +++ /dev/null @@ -1,347 +0,0 @@ -defmodule BerrypodWeb.ShopComponents.SettingsEditor do - @moduledoc """ - Shared settings editor components used in the on-site editor panel. - - Shows context-specific settings based on the current page type: - - Custom CMS pages: Title, slug, visibility, SEO meta, navigation - - System pages (home, about, etc.): Page info only (editing done in admin) - - Product pages: Read-only product info, link to admin - - Collection pages: Read-only collection info, link to admin - - Components emit events with a configurable prefix: - - `settings_update_page` (phx-change/phx-submit) - - The event prefix is controlled by `@event_prefix`: - - `"settings_"` (default) for on-site editor context - """ - - use Phoenix.Component - - import BerrypodWeb.CoreComponents, only: [icon: 1] - - # ── Main Editor Component ──────────────────────────────────────────── - - @doc """ - Renders the settings editor panel. - - Content varies based on the page type (custom, system, product, collection). - """ - attr :page, :map, default: nil - attr :product, :map, default: nil - attr :collection_title, :string, default: nil - attr :live_action, :atom, default: nil - attr :settings_form, :any, default: nil - attr :settings_dirty, :boolean, default: false - attr :settings_save_status, :atom, default: :idle - attr :event_prefix, :string, default: "settings_" - - def settings_editor(assigns) do - ~H""" -
- <%= cond do %> - <% @live_action == :product and @product -> %> - <.product_settings product={@product} /> - <% @live_action == :collection and @collection_title -> %> - <.collection_settings collection_title={@collection_title} /> - <% @page && @page[:type] == "custom" -> %> - <.custom_page_settings - page={@page} - form={@settings_form} - dirty={@settings_dirty} - save_status={@settings_save_status} - event_prefix={@event_prefix} - /> - <% @page -> %> - <.system_page_settings page={@page} /> - <% true -> %> - <.no_settings_view /> - <% end %> -
- """ - end - - # ── Custom Page Settings ───────────────────────────────────────────── - - attr :page, :map, required: true - attr :form, :any, default: nil - attr :dirty, :boolean, default: false - attr :save_status, :atom, default: :idle - attr :event_prefix, :string, default: "settings_" - - defp custom_page_settings(assigns) do - # Use form values if available, otherwise fall back to page values - form = assigns.form || %{} - - assigns = - assigns - |> assign(:form_title, form["title"] || assigns.page.title || "") - |> assign(:form_slug, form["slug"] || assigns.page.slug || "") - |> assign(:form_meta, form["meta_description"] || assigns.page.meta_description || "") - |> assign(:form_published, form_checked?(form, "published", assigns.page.published)) - |> assign(:form_show_in_nav, form_checked?(form, "show_in_nav", assigns.page.show_in_nav)) - |> assign(:form_nav_label, form["nav_label"] || assigns.page.nav_label || "") - |> assign( - :form_nav_position, - form["nav_position"] || to_string(assigns.page.nav_position || 0) - ) - - ~H""" -
-
"validate_page"} - phx-submit={@event_prefix <> "save_page"} - > -
- - -
- -
- -
- / - -
-
- -
- - -

Shown in search results. Keep under 160 characters.

-
- -
- -

Unpublished pages are only visible to admins.

-
- -
- -
- -
-
-
- - -
-
- - -
-
-
- -
- - <.save_status_indicator status={@save_status} /> -
-
- - -
- """ - end - - # ── System Page Settings ───────────────────────────────────────────── - - attr :page, :map, required: true - - defp system_page_settings(assigns) do - ~H""" -
-
- -

{@page.title}

-
- -
- -

System page

-
- -
-

- This is a built-in page. Edit its content using the Page tab, - or manage pages in admin. -

-
-
- """ - end - - # ── Product Page Settings ──────────────────────────────────────────── - - attr :product, :map, required: true - - defp product_settings(assigns) do - ~H""" -
-
- -

{@product.title}

-
- -
- -

{provider_label(@product)}

-
- -
-

- Product details are synced from your print provider. - View in admin - to see pricing, variants, and images. -

-
-
- """ - end - - defp provider_label(product) do - # provider_type is on the association, not the product itself - provider_type = - case product do - %{provider_connection: %{provider_type: type}} when is_binary(type) -> type - _ -> nil - end - - case provider_type do - "printify" -> "Printify" - "printful" -> "Printful" - _ -> "Print provider" - end - end - - # ── Collection Page Settings ───────────────────────────────────────── - - attr :collection_title, :string, required: true - - defp collection_settings(assigns) do - ~H""" -
-
- -

{@collection_title}

-
- -
-

- Collection pages show products filtered by category. - Edit the page layout using the Page tab. -

-
-
- """ - end - - # ── No Settings View ───────────────────────────────────────────────── - - defp no_settings_view(assigns) do - ~H""" -
-
-

- This page doesn't have editable settings. - Shop settings - can be changed in admin. -

-
-
- """ - end - - # ── Helper Functions ───────────────────────────────────────────────── - - # Check if a form checkbox should be checked - # Handles both form params (strings) and page values (booleans) - defp form_checked?(form, key, page_value) when is_map(form) do - case form[key] do - "true" -> true - "false" -> false - nil -> page_value == true - _ -> page_value == true - end - end - - # ── Helper Components ──────────────────────────────────────────────── - - attr :status, :atom, required: true - - defp save_status_indicator(assigns) do - ~H""" - - <.icon name="hero-check-mini" class="size-4" /> Saved - - - <.icon name="hero-exclamation-circle-mini" class="size-4" /> Error - - """ - end -end diff --git a/lib/berrypod_web/page_renderer.ex b/lib/berrypod_web/page_renderer.ex index bb83188..de05740 100644 --- a/lib/berrypod_web/page_renderer.ex +++ b/lib/berrypod_web/page_renderer.ex @@ -120,9 +120,6 @@ defmodule BerrypodWeb.PageRenderer do theme_editor_contrast_warning={Map.get(assigns, :theme_editor_contrast_warning, :ok)} uploads={Map.get(assigns, :uploads)} site_name={Map.get(assigns, :site_name, "")} - product={assigns[:product]} - collection_title={assigns[:collection_title]} - live_action={assigns[:live_action]} settings_form={Map.get(assigns, :settings_form)} settings_dirty={Map.get(assigns, :settings_dirty, false)} settings_save_status={Map.get(assigns, :settings_save_status, :idle)} @@ -159,9 +156,6 @@ defmodule BerrypodWeb.PageRenderer do attr :theme_editor_contrast_warning, :atom, default: :ok attr :uploads, :map, default: nil attr :site_name, :string, default: "" - attr :product, :map, default: nil - attr :collection_title, :string, default: nil - attr :live_action, :atom, default: nil attr :settings_form, :map, default: nil attr :settings_dirty, :boolean, default: false attr :settings_save_status, :atom, default: :idle @@ -185,6 +179,9 @@ defmodule BerrypodWeb.PageRenderer do editor_image_picker_images={@editor_image_picker_images} editor_image_picker_search={@editor_image_picker_search} editor_at_defaults={@editor_at_defaults} + settings_form={@settings_form} + settings_dirty={@settings_dirty} + settings_save_status={@settings_save_status} /> """ end @@ -206,21 +203,6 @@ defmodule BerrypodWeb.PageRenderer do """ end - defp editor_panel_content(%{editor_active_tab: :settings} = assigns) do - # Legacy settings tab - will be removed once page settings are merged into Page tab - ~H""" - - """ - end - defp editor_panel_content(%{editor_active_tab: :site} = assigns) do ~H""" + <%!-- Page settings for custom pages --%> + <.page_settings_section + :if={@page[:type] == "custom"} + page={@page} + form={@settings_form} + dirty={@settings_dirty} + save_status={@settings_save_status} + /> + <%!-- Block list --%>
<.block_card @@ -370,6 +364,141 @@ defmodule BerrypodWeb.PageRenderer do """ end + # Page settings section for custom pages (collapsible) + attr :page, :map, required: true + attr :form, :map, default: nil + attr :dirty, :boolean, default: false + attr :save_status, :atom, default: :idle + + defp page_settings_section(assigns) do + form = assigns.form || %{} + + assigns = + assigns + |> assign(:form_title, form["title"] || assigns.page.title || "") + |> assign(:form_slug, form["slug"] || assigns.page.slug || "") + |> assign(:form_meta, form["meta_description"] || assigns.page.meta_description || "") + |> assign(:form_published, form_checked?(form, "published", assigns.page.published)) + |> assign(:form_show_in_nav, form_checked?(form, "show_in_nav", assigns.page.show_in_nav)) + |> assign(:form_nav_label, form["nav_label"] || assigns.page.nav_label || "") + |> assign( + :form_nav_position, + form["nav_position"] || to_string(assigns.page.nav_position || 0) + ) + + ~H""" +
+ + <.icon name="hero-cog-6-tooth-mini" class="size-4" /> + Page settings + <.icon name="hero-chevron-down-mini" class="size-4 page-settings-chevron" /> + + +
+
+ + +
+ +
+ +
+ / + +
+
+ +
+ + +
+ +
+ + + +
+ +
+
+ + +
+
+ + +
+
+
+
+ """ + end + + defp form_checked?(form, key, page_value) when is_map(form) do + case form[key] do + "true" -> true + "false" -> false + nil -> page_value == true + _ -> page_value == true + end + end + # ── Block dispatch ────────────────────────────────────────────── defp render_block(%{block: %{"type" => "hero"}} = assigns) do