add nav editors to Site tab with live preview
All checks were successful
deploy / deploy (push) Successful in 3m27s

- Add header and footer nav editors to Site tab with drag-to-reorder,
  add/remove items, and destination picker (pages, collections, external)
- Live preview updates as you edit nav items
- Remove legacy /admin/navigation page and controller (was saving to
  Settings table, now uses nav_items table)
- Update error_html.ex and pages/editor.ex to load nav from nav_items table
- Update link_scanner to read from nav_items table, edit path now /?edit=site
- Add Site.default_header_nav/0 and default_footer_nav/0 for previews/errors
- Remove fallback logic from theme_hook.ex (database is now source of truth)
- Seed default nav items and social links during setup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-28 22:19:48 +00:00
parent 5a5103bc42
commit 7c07805df8
24 changed files with 1068 additions and 1130 deletions

View File

@@ -88,18 +88,23 @@ defmodule BerrypodWeb.ShopComponents.Layout do
base = Map.take(assigns, @layout_keys)
# When site editor is active, use in-memory values for live preview
# The site_* assigns are the editor's working copies, while announcement_*
# and social_links are the database-loaded values from theme_hook
# The site_state assigns is the editor's working copy, while announcement_*,
# nav items, and social_links are the database-loaded values from theme_hook
# Only override when site_editing is true (editor has loaded site state)
if assigns[:site_editing] do
# Convert raw SocialLink structs to shop format
social_links = format_social_links_for_shop(assigns[:site_social_links] || [])
if assigns[:site_editing] && assigns[:site_state] do
state = assigns[:site_state]
# Convert raw structs to shop format
social_links = format_social_links_for_shop(state.social_links || [])
header_nav = format_nav_items_for_shop(state.header_nav || [])
footer_nav = format_nav_items_for_shop(state.footer_nav || [])
base
|> Map.put(:announcement_text, assigns[:site_announcement_text])
|> Map.put(:announcement_link, assigns[:site_announcement_link])
|> Map.put(:announcement_style, assigns[:site_announcement_style])
|> Map.put(:announcement_text, state.announcement_text)
|> Map.put(:announcement_link, state.announcement_link)
|> Map.put(:announcement_style, state.announcement_style)
|> Map.put(:social_links, social_links)
|> Map.put(:header_nav_items, header_nav)
|> Map.put(:footer_nav_items, footer_nav)
else
base
end
@@ -122,6 +127,41 @@ defmodule BerrypodWeb.ShopComponents.Layout do
end)
end
# Convert raw NavItem structs to the format expected by shop components
# Filters out items with empty labels (incomplete entries still being edited)
defp format_nav_items_for_shop(items) do
items
|> Enum.reject(fn item -> is_nil(item.label) or item.label == "" end)
|> Enum.map(fn item ->
slug = extract_slug_from_url(item.url)
base = %{
"label" => item.label,
"href" => item.url,
"slug" => slug
}
# Add active_slugs for Shop nav item to highlight on collection and pdp pages
if slug == "collection" do
Map.put(base, "active_slugs", ["collection", "pdp"])
else
base
end
end)
end
# Extract a slug from a URL for nav item matching
defp extract_slug_from_url(url) when is_binary(url) do
cond do
url == "/" -> "home"
String.starts_with?(url, "/collections") -> "collection"
String.starts_with?(url, "/products") -> "pdp"
true -> url |> String.trim_leading("/") |> String.split("/") |> List.first() || ""
end
end
defp extract_slug_from_url(_), do: ""
# Social
defp platform_display_label("instagram"), do: "Instagram"
defp platform_display_label("threads"), do: "Threads"