From 9fb836ca0d94cd8db58fe1fbf1b8480a0a101d50 Mon Sep 17 00:00:00 2001 From: Jamey Greenwood Date: Mon, 19 Jan 2026 23:26:41 +0000 Subject: [PATCH] feat: add /collections/:slug routes with category filtering - Add ShopLive.Collection LiveView for collection pages - Add products_by_category/1 and category_by_slug/1 to PreviewData - Support /collections/all for all products view - Remove /products route and ShopLive.Products (replaced by collections) - Update all links to use /collections/all instead of /products - Update category nav to link to /collections/:slug - Update PDP breadcrumb to link to specific collection URL structure now follows Shopify convention: - /collections/all - All products - /collections/art-prints - Art Prints collection - /collections/apparel - Apparel collection - etc. Co-Authored-By: Claude Opus 4.5 --- lib/simpleshop_theme/theme/preview_data.ex | 28 +++ .../components/page_templates/pdp.html.heex | 2 +- .../components/shop_components.ex | 18 +- .../live/shop_live/collection.ex | 169 ++++++++++++++++++ .../live/shop_live/products.ex | 60 ------- lib/simpleshop_theme_web/router.ex | 2 +- 6 files changed, 208 insertions(+), 71 deletions(-) create mode 100644 lib/simpleshop_theme_web/live/shop_live/collection.ex delete mode 100644 lib/simpleshop_theme_web/live/shop_live/products.ex diff --git a/lib/simpleshop_theme/theme/preview_data.ex b/lib/simpleshop_theme/theme/preview_data.ex index 6b0fe94..f97c42a 100644 --- a/lib/simpleshop_theme/theme/preview_data.ex +++ b/lib/simpleshop_theme/theme/preview_data.ex @@ -114,6 +114,34 @@ defmodule SimpleshopTheme.Theme.PreviewData do end end + @doc """ + Returns a category by its slug. + + Returns nil if not found. + """ + def category_by_slug(slug) do + Enum.find(categories(), fn cat -> cat.slug == slug end) + end + + @doc """ + Returns products filtered by category slug. + + If slug is nil or "all", returns all products. + """ + def products_by_category(nil), do: products() + def products_by_category("all"), do: products() + + def products_by_category(slug) do + case category_by_slug(slug) do + nil -> + [] + + category -> + products() + |> Enum.filter(fn product -> product.category == category.name end) + end + end + @doc """ Checks if the shop has real products. diff --git a/lib/simpleshop_theme_web/components/page_templates/pdp.html.heex b/lib/simpleshop_theme_web/components/page_templates/pdp.html.heex index 7d93ad3..7d0d0ce 100644 --- a/lib/simpleshop_theme_web/components/page_templates/pdp.html.heex +++ b/lib/simpleshop_theme_web/components/page_templates/pdp.html.heex @@ -10,7 +10,7 @@
<.breadcrumb items={[ %{label: "Home", page: "home", href: "/"}, - %{label: @product.category, page: "collection", href: "/products"}, + %{label: @product.category, page: "collection", href: "/collections/#{@product.category |> String.downcase() |> String.replace(" ", "-")}"}, %{label: @product.name, current: true} ]} mode={@mode} /> diff --git a/lib/simpleshop_theme_web/components/shop_components.ex b/lib/simpleshop_theme_web/components/shop_components.ex index f359ef1..d64c791 100644 --- a/lib/simpleshop_theme_web/components/shop_components.ex +++ b/lib/simpleshop_theme_web/components/shop_components.ex @@ -184,9 +184,9 @@ defmodule SimpleshopThemeWeb.ShopComponents do
  • New arrivals
  • Best sellers
  • <% else %> -
  • All products
  • -
  • New arrivals
  • -
  • Best sellers
  • +
  • All products
  • +
  • New arrivals
  • +
  • Best sellers
  • <% end %> @@ -327,7 +327,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do Contact <% else %> Home - Shop + Shop About Contact <% end %> @@ -1078,7 +1078,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do <% else %> - +
    <% else %> @@ -1812,7 +1812,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do <.breadcrumb items={[ %{label: "Home", page: "home", href: "/"}, - %{label: "Art Prints", page: "collection", href: "/products?category=art-prints"}, + %{label: "Art Prints", page: "collection", href: "/collections/art-prints"}, %{label: "Mountain Sunrise", current: true} ]} mode={:preview} /> """ diff --git a/lib/simpleshop_theme_web/live/shop_live/collection.ex b/lib/simpleshop_theme_web/live/shop_live/collection.ex new file mode 100644 index 0000000..a30a6fa --- /dev/null +++ b/lib/simpleshop_theme_web/live/shop_live/collection.ex @@ -0,0 +1,169 @@ +defmodule SimpleshopThemeWeb.ShopLive.Collection do + use SimpleshopThemeWeb, :live_view + + alias SimpleshopTheme.Settings + alias SimpleshopTheme.Media + alias SimpleshopTheme.Theme.{CSSCache, CSSGenerator, PreviewData} + + @impl true + def mount(_params, _session, socket) do + theme_settings = Settings.get_theme_settings() + + generated_css = + case CSSCache.get() do + {:ok, css} -> css + :miss -> + css = CSSGenerator.generate(theme_settings) + CSSCache.put(css) + css + end + + logo_image = Media.get_logo() + header_image = Media.get_header() + + socket = + socket + |> assign(:theme_settings, theme_settings) + |> assign(:generated_css, generated_css) + |> assign(:logo_image, logo_image) + |> assign(:header_image, header_image) + |> assign(:mode, :shop) + |> assign(:cart_items, []) + |> assign(:cart_count, 0) + |> assign(:cart_subtotal, "£0.00") + |> assign(:categories, PreviewData.categories()) + + {:ok, socket} + end + + @impl true + def handle_params(%{"slug" => "all"}, _uri, socket) do + {:noreply, + socket + |> assign(:page_title, "All Products") + |> assign(:collection_title, "All Products") + |> assign(:current_category, nil) + |> assign(:products, PreviewData.products())} + end + + def handle_params(%{"slug" => slug}, _uri, socket) do + case PreviewData.category_by_slug(slug) do + nil -> + {:noreply, + socket + |> put_flash(:error, "Collection not found") + |> push_navigate(to: ~p"/collections/all")} + + category -> + products = PreviewData.products_by_category(slug) + + {:noreply, + socket + |> assign(:page_title, category.name) + |> assign(:collection_title, category.name) + |> assign(:current_category, category) + |> assign(:products, products)} + end + end + + @impl true + def render(assigns) do + ~H""" +
    + + + <%= if @theme_settings.announcement_bar do %> + + <% end %> + + + +
    + + +
    + <.collection_filter_bar categories={@categories} current_slug={@current_category && @current_category.slug} /> + + + <%= for product <- @products do %> + + <% end %> + + + <%= if @products == [] do %> +
    +

    No products found in this collection.

    + <.link navigate={~p"/collections/all"} class="mt-4 inline-block underline" style="color: var(--t-text-accent);"> + View all products + +
    + <% end %> +
    +
    + + + + + + +
    + """ + end + + defp collection_filter_bar(assigns) do + ~H""" + + """ + end +end diff --git a/lib/simpleshop_theme_web/live/shop_live/products.ex b/lib/simpleshop_theme_web/live/shop_live/products.ex deleted file mode 100644 index 34e5eee..0000000 --- a/lib/simpleshop_theme_web/live/shop_live/products.ex +++ /dev/null @@ -1,60 +0,0 @@ -defmodule SimpleshopThemeWeb.ShopLive.Products do - use SimpleshopThemeWeb, :live_view - - alias SimpleshopTheme.Settings - alias SimpleshopTheme.Media - alias SimpleshopTheme.Theme.{CSSCache, CSSGenerator, PreviewData} - - @impl true - def mount(_params, _session, socket) do - theme_settings = Settings.get_theme_settings() - - generated_css = - case CSSCache.get() do - {:ok, css} -> css - :miss -> - css = CSSGenerator.generate(theme_settings) - CSSCache.put(css) - css - end - - logo_image = Media.get_logo() - header_image = Media.get_header() - - preview_data = %{ - products: PreviewData.products(), - categories: PreviewData.categories() - } - - socket = - socket - |> assign(:page_title, "Products") - |> assign(:theme_settings, theme_settings) - |> assign(:generated_css, generated_css) - |> assign(:logo_image, logo_image) - |> assign(:header_image, header_image) - |> assign(:preview_data, preview_data) - |> assign(:mode, :shop) - |> assign(:cart_items, []) - |> assign(:cart_count, 0) - |> assign(:cart_subtotal, "£0.00") - - {:ok, socket} - end - - @impl true - def render(assigns) do - ~H""" - - """ - end -end diff --git a/lib/simpleshop_theme_web/router.ex b/lib/simpleshop_theme_web/router.ex index 9e31189..1cf72f6 100644 --- a/lib/simpleshop_theme_web/router.ex +++ b/lib/simpleshop_theme_web/router.ex @@ -29,7 +29,7 @@ defmodule SimpleshopThemeWeb.Router do live "/", ShopLive.Home, :index live "/about", ShopLive.About, :index live "/contact", ShopLive.Contact, :index - live "/products", ShopLive.Products, :index + live "/collections/:slug", ShopLive.Collection, :show live "/products/:id", ShopLive.ProductShow, :show live "/cart", ShopLive.Cart, :index end