diff --git a/assets/css/admin/components.css b/assets/css/admin/components.css index 5feb7f9..c49f8e4 100644 --- a/assets/css/admin/components.css +++ b/assets/css/admin/components.css @@ -1465,7 +1465,7 @@ @media (min-width: 40em) { border-radius: 0.75rem; - max-width: 28rem; + max-width: 36rem; } } @@ -1493,9 +1493,10 @@ } .block-picker-item { - display: flex; - align-items: center; - gap: 0.5rem; + display: grid; + grid-template-columns: 1.25rem 1fr; + gap: 0.25rem 0.5rem; + align-items: start; padding: 0.625rem 0.75rem; border: 1px solid var(--t-border-default); border-radius: 0.375rem; @@ -1513,6 +1514,18 @@ } } +.block-picker-item > .size-5 { + grid-row: 1 / -1; + margin-top: 0.0625rem; +} + +.block-picker-item-desc { + grid-column: 2; + font-size: 0.6875rem; + line-height: 1.3; + color: color-mix(in oklch, var(--t-text-primary) 55%, transparent); +} + .block-picker-empty { grid-column: 1 / -1; text-align: center; @@ -2280,6 +2293,20 @@ /* Navigation editor */ +.nav-editor-labels { + display: flex; + gap: 0.5rem; + padding: 0 0.5rem; + font-size: 0.75rem; + font-weight: 500; + color: color-mix(in oklch, var(--t-text-primary) 55%, transparent); + + & span { + flex: 1; + min-width: 0; + } +} + .nav-editor-item { display: flex; align-items: center; diff --git a/assets/js/app.js b/assets/js/app.js index 4f2415b..2f20a0d 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -662,6 +662,24 @@ const EditorKeyboard = { } window.addEventListener("beforeunload", this._beforeUnload) + // Intercept LiveView navigation clicks when editor has unsaved changes. + // Uses capture phase to fire before LiveView's own click handler. + this._clickGuard = (e) => { + if (this.el.dataset.dirty !== "true") return + + const link = e.target.closest("a[data-phx-link]") + if (!link) return + + // Don't block clicks inside the editor itself (e.g. block controls) + if (this.el.contains(link)) return + + if (!window.confirm("You have unsaved changes that will be lost. Leave anyway?")) { + e.preventDefault() + e.stopImmediatePropagation() + } + } + document.addEventListener("click", this._clickGuard, true) + const prefix = this.el.dataset.eventPrefix || "" this._keydown = (e) => { if ((e.ctrlKey || e.metaKey) && e.key === "z") { @@ -678,6 +696,7 @@ const EditorKeyboard = { destroyed() { window.removeEventListener("beforeunload", this._beforeUnload) + document.removeEventListener("click", this._clickGuard, true) document.removeEventListener("keydown", this._keydown) } } diff --git a/lib/berrypod/pages/block_types.ex b/lib/berrypod/pages/block_types.ex index 0cfda1a..83a40b9 100644 --- a/lib/berrypod/pages/block_types.ex +++ b/lib/berrypod/pages/block_types.ex @@ -16,6 +16,7 @@ defmodule Berrypod.Pages.BlockTypes do "hero" => %{ name: "Hero banner", + description: "Full-width banner with title, description and call-to-action buttons", icon: "hero-megaphone", allowed_on: :all, settings_schema: [ @@ -46,6 +47,7 @@ defmodule Berrypod.Pages.BlockTypes do }, "featured_products" => %{ name: "Featured products", + description: "Grid of product cards — choose how many and which style", icon: "hero-star", allowed_on: :all, settings_schema: [ @@ -87,6 +89,7 @@ defmodule Berrypod.Pages.BlockTypes do }, "image_text" => %{ name: "Image + text", + description: "Side-by-side image and text with optional link", icon: "hero-photo", allowed_on: :all, settings_schema: [ @@ -100,24 +103,28 @@ defmodule Berrypod.Pages.BlockTypes do }, "category_nav" => %{ name: "Category navigation", + description: "Clickable category pills linking to filtered product collections", icon: "hero-squares-2x2", allowed_on: :all, settings_schema: [] }, "newsletter_card" => %{ name: "Newsletter signup", + description: "Email signup form for collecting subscriber addresses", icon: "hero-envelope", allowed_on: :all, settings_schema: [] }, "social_links_card" => %{ name: "Social links", + description: "Icons linking to your social media profiles", icon: "hero-share", allowed_on: :all, settings_schema: [] }, "info_card" => %{ name: "Info card", + description: "Key-value list for shop details like materials, sizing or delivery info", icon: "hero-information-circle", allowed_on: :all, settings_schema: [ @@ -136,12 +143,14 @@ defmodule Berrypod.Pages.BlockTypes do }, "trust_badges" => %{ name: "Trust badges", + description: "Icons for free shipping, secure payment and quality guarantees", icon: "hero-shield-check", allowed_on: :all, settings_schema: [] }, "reviews_section" => %{ name: "Customer reviews", + description: "Star ratings and customer testimonials", icon: "hero-chat-bubble-left-right", allowed_on: :all, settings_schema: [], @@ -149,6 +158,7 @@ defmodule Berrypod.Pages.BlockTypes do }, "spacer" => %{ name: "Spacer", + description: "Vertical breathing room between blocks", icon: "hero-arrows-up-down", allowed_on: :all, settings_schema: [ @@ -163,6 +173,7 @@ defmodule Berrypod.Pages.BlockTypes do }, "divider" => %{ name: "Divider", + description: "Horizontal line, dots or fade to separate sections", icon: "hero-minus", allowed_on: :all, settings_schema: [ @@ -177,6 +188,7 @@ defmodule Berrypod.Pages.BlockTypes do }, "button" => %{ name: "Button", + description: "Standalone call-to-action button linking to any page", icon: "hero-cursor-arrow-rays", allowed_on: :all, settings_schema: [ @@ -200,6 +212,7 @@ defmodule Berrypod.Pages.BlockTypes do }, "video_embed" => %{ name: "Video embed", + description: "Embedded YouTube or Vimeo video with optional caption", icon: "hero-play", allowed_on: :all, settings_schema: [ @@ -219,24 +232,28 @@ defmodule Berrypod.Pages.BlockTypes do "product_hero" => %{ name: "Product hero", + description: "Product image gallery, title, price and add-to-cart button", icon: "hero-cube", allowed_on: ["pdp"], settings_schema: [] }, "breadcrumb" => %{ name: "Breadcrumb", + description: "Navigation trail showing Home > Category > Product", icon: "hero-chevron-right", allowed_on: ["pdp"], settings_schema: [] }, "product_details" => %{ name: "Product details", + description: "Full product description text", icon: "hero-document-text", allowed_on: ["pdp"], settings_schema: [] }, "related_products" => %{ name: "Related products", + description: "Products from the same category", icon: "hero-squares-plus", allowed_on: ["pdp"], settings_schema: [], @@ -247,18 +264,21 @@ defmodule Berrypod.Pages.BlockTypes do "collection_header" => %{ name: "Collection header", + description: "Category title and product count", icon: "hero-tag", allowed_on: ["collection"], settings_schema: [] }, "filter_bar" => %{ name: "Filter bar", + description: "Category filter pills and sort dropdown", icon: "hero-funnel", allowed_on: ["collection"], settings_schema: [] }, "product_grid" => %{ name: "Product grid", + description: "Responsive grid of all products in the collection", icon: "hero-squares-2x2", allowed_on: ["collection"], settings_schema: [] @@ -268,12 +288,14 @@ defmodule Berrypod.Pages.BlockTypes do "cart_items" => %{ name: "Cart items", + description: "List of items in the customer's cart with quantity controls", icon: "hero-shopping-cart", allowed_on: ["cart"], settings_schema: [] }, "order_summary" => %{ name: "Order summary", + description: "Subtotal, shipping estimate and checkout button", icon: "hero-calculator", allowed_on: ["cart"], settings_schema: [] @@ -283,6 +305,7 @@ defmodule Berrypod.Pages.BlockTypes do "contact_form" => %{ name: "Contact form", + description: "Name, email and message form that sends to your inbox", icon: "hero-envelope", allowed_on: ["contact"], settings_schema: [ @@ -291,6 +314,7 @@ defmodule Berrypod.Pages.BlockTypes do }, "order_tracking_card" => %{ name: "Order tracking", + description: "Lets customers look up their order status", icon: "hero-truck", allowed_on: ["contact"], settings_schema: [] @@ -300,6 +324,7 @@ defmodule Berrypod.Pages.BlockTypes do "content_body" => %{ name: "Page content", + description: "Rich text block with optional image — the main body of a content page", icon: "hero-document-text", allowed_on: :all, settings_schema: [ @@ -319,6 +344,7 @@ defmodule Berrypod.Pages.BlockTypes do "checkout_result" => %{ name: "Checkout result", + description: "Order confirmation message with order number", icon: "hero-check-circle", allowed_on: ["checkout_success"], settings_schema: [] @@ -328,6 +354,7 @@ defmodule Berrypod.Pages.BlockTypes do "order_card" => %{ name: "Order cards", + description: "List of past orders with status and totals", icon: "hero-clipboard-document-list", allowed_on: ["orders"], settings_schema: [] @@ -337,6 +364,7 @@ defmodule Berrypod.Pages.BlockTypes do "order_detail_card" => %{ name: "Order detail", + description: "Full order breakdown with items, shipping and payment info", icon: "hero-clipboard-document", allowed_on: ["order_detail"], settings_schema: [] @@ -346,6 +374,7 @@ defmodule Berrypod.Pages.BlockTypes do "search_results" => %{ name: "Search results", + description: "Product search results grid with thumbnails and prices", icon: "hero-magnifying-glass", allowed_on: ["search"], settings_schema: [] diff --git a/lib/berrypod_web/components/block_editor_components.ex b/lib/berrypod_web/components/block_editor_components.ex index 20c5446..eb14a2d 100644 --- a/lib/berrypod_web/components/block_editor_components.ex +++ b/lib/berrypod_web/components/block_editor_components.ex @@ -416,7 +416,8 @@ defmodule BerrypodWeb.BlockEditorComponents do filtered = assigns.allowed_blocks |> Enum.filter(fn {_type, def} -> - filter == "" or String.contains?(String.downcase(def.name), filter) + filter == "" or String.contains?(String.downcase(def.name), filter) or + String.contains?(String.downcase(Map.get(def, :description, "")), filter) end) |> Enum.sort_by(fn {_type, def} -> def.name end) @@ -454,7 +455,10 @@ defmodule BerrypodWeb.BlockEditorComponents do class="block-picker-item" > <.icon name={def.icon} class="size-5" /> - {def.name} + {def.name} + + {def.description} +

diff --git a/lib/berrypod_web/components/layouts/admin.html.heex b/lib/berrypod_web/components/layouts/admin.html.heex index 8d187d9..a7a07f4 100644 --- a/lib/berrypod_web/components/layouts/admin.html.heex +++ b/lib/berrypod_web/components/layouts/admin.html.heex @@ -91,7 +91,7 @@ navigate={~p"/admin/providers"} class={admin_nav_active?(@current_path, "/admin/providers")} > - <.icon name="hero-link" class="size-5" /> Providers + <.icon name="hero-link" class="size-5" /> Print providers

  • diff --git a/lib/berrypod_web/live/admin/navigation.ex b/lib/berrypod_web/live/admin/navigation.ex index fa27630..19fd398 100644 --- a/lib/berrypod_web/live/admin/navigation.ex +++ b/lib/berrypod_web/live/admin/navigation.ex @@ -158,13 +158,19 @@ defmodule BerrypodWeb.Admin.Navigation do
    +