From 209ae7aee79e8f8e99d35c59a99db0eca20e003b Mon Sep 17 00:00:00 2001 From: jamey Date: Wed, 11 Feb 2026 08:17:19 +0000 Subject: [PATCH] fix navigation links, footer categories, product card structure, and social icons - add missing cta_href to hero section and error page CTAs - replace hardcoded footer shop links with real product categories - restructure product cards with stretched-link pattern so category badges link to their collection page - unify social icons: footer and contact page share the same default links from a single source in content.ex - add search implementation plan (docs/plans/search.md, deferred) Co-Authored-By: Claude Opus 4.6 --- PROGRESS.md | 12 +- assets/css/theme-semantic.css | 8 ++ docs/plans/search.md | 61 ++++++++++ lib/simpleshop_theme_web/cart_hook.ex | 2 + .../components/page_templates/cart.html.heex | 1 + .../page_templates/checkout_success.html.heex | 1 + .../page_templates/collection.html.heex | 1 + .../page_templates/contact.html.heex | 9 +- .../page_templates/content.html.heex | 1 + .../components/page_templates/error.html.heex | 3 + .../components/page_templates/home.html.heex | 3 + .../components/page_templates/pdp.html.heex | 1 + .../components/shop_components/content.ex | 20 ++-- .../components/shop_components/layout.ex | 111 +++++------------ .../components/shop_components/product.ex | 113 +++++++++--------- 15 files changed, 186 insertions(+), 161 deletions(-) create mode 100644 docs/plans/search.md diff --git a/PROGRESS.md b/PROGRESS.md index 28f5284..066e98b 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -53,11 +53,10 @@ Issues found during hands-on testing of the deployed prod site on mobile and des - [ ] Shipping costs: add Stripe shipping options or query Printify for dynamic rates ### Navigation & links -- [ ] Search doesn't work (modal opens but no results/functionality) -- [ ] "Shop the collection" button/link does nothing -- [ ] Footer "New arrivals" and "Best sellers" links don't go anywhere -- [ ] Should be able to tap a category badge on product cards to go to that category -- [ ] Footer social icons should match the "Find me on" icons from the contact page +- [x] "Shop the collection" button/link does nothing +- [x] Footer "New arrivals" and "Best sellers" links don't go anywhere +- [x] Should be able to tap a category badge on product cards to go to that category +- [x] Footer social icons should match the "Find me on" icons from the contact page ### Collections / all products - [ ] Categories on all-products page are too spaced out @@ -71,6 +70,9 @@ Issues found during hands-on testing of the deployed prod site on mobile and des ### Errors - [ ] 404 page is broken +### Search (deferred — after usability fixes) +- [ ] Search doesn't work (modal opens but no results/functionality) — see [docs/plans/search.md](docs/plans/search.md) + ## Roadmap ### Tier 1 — MVP (can take real orders and fulfil them) diff --git a/assets/css/theme-semantic.css b/assets/css/theme-semantic.css index 4ac6adb..c0e9db5 100644 --- a/assets/css/theme-semantic.css +++ b/assets/css/theme-semantic.css @@ -229,6 +229,14 @@ outline-offset: 2px; } +/* Stretched link: makes a parent container fully clickable via ::after overlay */ +.stretched-link::after { + content: ""; + position: absolute; + inset: 0; + z-index: 0; +} + /* Nav link styling with active state indicator */ .shop-nav a, .shop-nav span { diff --git a/docs/plans/search.md b/docs/plans/search.md new file mode 100644 index 0000000..31fea03 --- /dev/null +++ b/docs/plans/search.md @@ -0,0 +1,61 @@ +# Plan: Implement product search in search modal + +Status: Pending (after usability fixes) + +## Overview + +The search modal UI shell exists but has zero functionality — no event bindings, no backend search, no results rendering. This plan adds live search across all products. + +## Approach + +Small catalog (print-on-demand, < 100 products) — search `PreviewData.products()` in memory. No DB full-text search needed. + +Product maps have: `.name`, `.category`, `.description`, `.slug`, `.price`, `.image_url`, `.image_id` + +## Changes + +### 1. CartHook — add search assigns + event handler +**File:** `lib/simpleshop_theme_web/cart_hook.ex` + +- Init assigns in `on_mount`: `search_results: []`, `search_query: ""` +- Handle `"search"` event (from `phx-keyup`): + - Empty/blank query → assign `search_results: []`, `search_query: ""` + - Non-empty → filter `PreviewData.products()` by name/category/description (case-insensitive substring match), take 6, assign results +- Handle `"close_search"` event → clear query + results + hide modal via JS + +### 2. shop_layout + search_modal — add search attrs and UI +**File:** `lib/simpleshop_theme_web/components/shop_components/layout.ex` + +**shop_layout:** +- Add optional attrs: `search_results` (default `[]`), `search_query` (default `""`) +- Pass them to `<.search_modal>` + +**search_modal:** +- Add `search_results` (list, default `[]`) and `search_query` (string, default `""`) attrs +- Add `name="query"`, `phx-keyup="search"`, `phx-debounce="200"`, `value={@search_query}` to the input +- On close button + backdrop click: also push `"close_search"` event +- Results section below input: + - Each result: link to `/products/{slug}` with product name, category, formatted price + - "No results found" when query non-empty but no matches + - Hint text only shown when no query +- Click on result: navigate to product, close modal (JS.hide + close_search event) + +### 3. Page templates — thread search assigns +**All 8 files in** `lib/simpleshop_theme_web/components/page_templates/` + +Add two lines to each `<.shop_layout>` call: +``` +search_results={assigns[:search_results] || []} +search_query={assigns[:search_query] || ""} +``` + +Same pattern as `cart_drawer_open` and `cart_status`. + +## Files to modify +1. `lib/simpleshop_theme_web/cart_hook.ex` +2. `lib/simpleshop_theme_web/components/shop_components/layout.ex` (shop_layout + search_modal) +3. All 8 page templates in `lib/simpleshop_theme_web/components/page_templates/` + +## Verification +- Browser: open search modal on multiple pages, type queries, verify results appear and link correctly +- `mix test` — all existing tests pass diff --git a/lib/simpleshop_theme_web/cart_hook.ex b/lib/simpleshop_theme_web/cart_hook.ex index a5770f2..3317cff 100644 --- a/lib/simpleshop_theme_web/cart_hook.ex +++ b/lib/simpleshop_theme_web/cart_hook.ex @@ -19,6 +19,7 @@ defmodule SimpleshopThemeWeb.CartHook do import Phoenix.LiveView, only: [attach_hook: 4, connected?: 1, push_event: 3] alias SimpleshopTheme.Cart + alias SimpleshopTheme.Theme.PreviewData def on_mount(:mount_cart, _params, session, socket) do cart_items = Cart.get_from_session(session) @@ -28,6 +29,7 @@ defmodule SimpleshopThemeWeb.CartHook do |> update_cart_assigns(cart_items) |> assign(:cart_drawer_open, false) |> assign(:cart_status, nil) + |> assign(:categories, PreviewData.categories()) |> attach_hook(:cart_events, :handle_event, &handle_cart_event/3) |> attach_hook(:cart_info, :handle_info, &handle_cart_info/2) diff --git a/lib/simpleshop_theme_web/components/page_templates/cart.html.heex b/lib/simpleshop_theme_web/components/page_templates/cart.html.heex index 670b588..51f3022 100644 --- a/lib/simpleshop_theme_web/components/page_templates/cart.html.heex +++ b/lib/simpleshop_theme_web/components/page_templates/cart.html.heex @@ -8,6 +8,7 @@ cart_subtotal={@cart_subtotal} cart_drawer_open={assigns[:cart_drawer_open] || false} cart_status={assigns[:cart_status]} + categories={assigns[:categories] || []} active_page="cart" >
diff --git a/lib/simpleshop_theme_web/components/page_templates/checkout_success.html.heex b/lib/simpleshop_theme_web/components/page_templates/checkout_success.html.heex index 125f108..6706a53 100644 --- a/lib/simpleshop_theme_web/components/page_templates/checkout_success.html.heex +++ b/lib/simpleshop_theme_web/components/page_templates/checkout_success.html.heex @@ -8,6 +8,7 @@ cart_subtotal={@cart_subtotal} cart_drawer_open={assigns[:cart_drawer_open] || false} cart_status={assigns[:cart_status]} + categories={assigns[:categories] || []} active_page="checkout" >
diff --git a/lib/simpleshop_theme_web/components/page_templates/collection.html.heex b/lib/simpleshop_theme_web/components/page_templates/collection.html.heex index bf971ea..e2b24db 100644 --- a/lib/simpleshop_theme_web/components/page_templates/collection.html.heex +++ b/lib/simpleshop_theme_web/components/page_templates/collection.html.heex @@ -8,6 +8,7 @@ cart_subtotal={@cart_subtotal} cart_drawer_open={assigns[:cart_drawer_open] || false} cart_status={assigns[:cart_status]} + categories={assigns[:categories] || []} active_page="collection" >
diff --git a/lib/simpleshop_theme_web/components/page_templates/contact.html.heex b/lib/simpleshop_theme_web/components/page_templates/contact.html.heex index bb80eda..716adc3 100644 --- a/lib/simpleshop_theme_web/components/page_templates/contact.html.heex +++ b/lib/simpleshop_theme_web/components/page_templates/contact.html.heex @@ -8,6 +8,7 @@ cart_subtotal={@cart_subtotal} cart_drawer_open={assigns[:cart_drawer_open] || false} cart_status={assigns[:cart_status]} + categories={assigns[:categories] || []} active_page="contact" >
@@ -34,13 +35,7 @@ <.newsletter_card /> - <.social_links_card links={[ - %{platform: :instagram, url: "https://instagram.com", label: "Instagram"}, - %{platform: :bluesky, url: "https://bsky.app", label: "Bluesky"}, - %{platform: :mastodon, url: "https://mastodon.social", label: "Mastodon"}, - %{platform: :kofi, url: "https://ko-fi.com", label: "Ko-fi"}, - %{platform: :github, url: "https://github.com", label: "GitHub"} - ]} /> + <.social_links_card />
diff --git a/lib/simpleshop_theme_web/components/page_templates/content.html.heex b/lib/simpleshop_theme_web/components/page_templates/content.html.heex index 7ff3ab2..cbc60a0 100644 --- a/lib/simpleshop_theme_web/components/page_templates/content.html.heex +++ b/lib/simpleshop_theme_web/components/page_templates/content.html.heex @@ -8,6 +8,7 @@ cart_subtotal={@cart_subtotal} cart_drawer_open={assigns[:cart_drawer_open] || false} cart_status={assigns[:cart_status]} + categories={assigns[:categories] || []} active_page={@active_page} >
diff --git a/lib/simpleshop_theme_web/components/page_templates/error.html.heex b/lib/simpleshop_theme_web/components/page_templates/error.html.heex index b301fee..c76d6e9 100644 --- a/lib/simpleshop_theme_web/components/page_templates/error.html.heex +++ b/lib/simpleshop_theme_web/components/page_templates/error.html.heex @@ -8,6 +8,7 @@ cart_subtotal={@cart_subtotal} cart_drawer_open={assigns[:cart_drawer_open] || false} cart_status={assigns[:cart_status]} + categories={assigns[:categories] || []} active_page="error" error_page > @@ -24,8 +25,10 @@ description={@error_description} cta_text="Go to Homepage" cta_page="home" + cta_href="/" secondary_cta_text="Browse Products" secondary_cta_page="collection" + secondary_cta_href="/collections/all" mode={@mode} /> diff --git a/lib/simpleshop_theme_web/components/page_templates/home.html.heex b/lib/simpleshop_theme_web/components/page_templates/home.html.heex index 0e870d2..389c7ac 100644 --- a/lib/simpleshop_theme_web/components/page_templates/home.html.heex +++ b/lib/simpleshop_theme_web/components/page_templates/home.html.heex @@ -8,6 +8,7 @@ cart_subtotal={@cart_subtotal} cart_drawer_open={assigns[:cart_drawer_open] || false} cart_status={assigns[:cart_status]} + categories={assigns[:categories] || []} active_page="home" >
@@ -16,6 +17,7 @@ description="Welcome to the SimpleShop demo store. This is where your hero text goes – something short and punchy about what makes your shop worth a browse." cta_text="Shop the collection" cta_page="collection" + cta_href="/collections/all" mode={@mode} /> @@ -34,6 +36,7 @@ image_url="/mockups/mountain-sunrise-print-3-800.webp" link_text="Learn more about the studio →" link_page="about" + link_href="/about" mode={@mode} />
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 14d9f6d..1036888 100644 --- a/lib/simpleshop_theme_web/components/page_templates/pdp.html.heex +++ b/lib/simpleshop_theme_web/components/page_templates/pdp.html.heex @@ -8,6 +8,7 @@ cart_subtotal={@cart_subtotal} cart_drawer_open={assigns[:cart_drawer_open] || false} cart_status={assigns[:cart_status]} + categories={assigns[:categories] || []} active_page="pdp" >
diff --git a/lib/simpleshop_theme_web/components/shop_components/content.ex b/lib/simpleshop_theme_web/components/shop_components/content.ex index ab8f62c..d9d53f6 100644 --- a/lib/simpleshop_theme_web/components/shop_components/content.ex +++ b/lib/simpleshop_theme_web/components/shop_components/content.ex @@ -5,6 +5,14 @@ defmodule SimpleshopThemeWeb.ShopComponents.Content do import SimpleshopThemeWeb.ShopComponents.Base + @default_social_links [ + %{platform: :instagram, url: "https://instagram.com", label: "Instagram"}, + %{platform: :bluesky, url: "https://bsky.app", label: "Bluesky"}, + %{platform: :mastodon, url: "https://mastodon.social", label: "Mastodon"}, + %{platform: :kofi, url: "https://ko-fi.com", label: "Ko-fi"}, + %{platform: :github, url: "https://github.com", label: "GitHub"} + ] + @doc """ Renders a content body container for long-form content pages (about, etc.). @@ -338,11 +346,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Content do """ attr :title, :string, default: "Find me on" - attr :links, :list, - default: [ - %{platform: :instagram, url: "#", label: "Instagram"}, - %{platform: :pinterest, url: "#", label: "Pinterest"} - ] + attr :links, :list, default: @default_social_links def social_links_card(assigns) do ~H""" @@ -381,11 +385,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Content do <.social_links /> <.social_links links={[%{platform: :instagram, url: "https://instagram.com/example", label: "Instagram"}]} /> """ - attr :links, :list, - default: [ - %{platform: :instagram, url: "https://instagram.com", label: "Instagram"}, - %{platform: :pinterest, url: "https://pinterest.com", label: "Pinterest"} - ] + attr :links, :list, default: @default_social_links def social_links(assigns) do ~H""" diff --git a/lib/simpleshop_theme_web/components/shop_components/layout.ex b/lib/simpleshop_theme_web/components/shop_components/layout.ex index baad948..18e8e02 100644 --- a/lib/simpleshop_theme_web/components/shop_components/layout.ex +++ b/lib/simpleshop_theme_web/components/shop_components/layout.ex @@ -65,6 +65,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do attr :cart_subtotal, :string, required: true attr :cart_drawer_open, :boolean, default: false attr :cart_status, :string, default: nil + attr :categories, :list, default: [] attr :active_page, :string, required: true attr :error_page, :boolean, default: false @@ -95,7 +96,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do {render_slot(@inner_block)} - <.shop_footer theme_settings={@theme_settings} mode={@mode} /> + <.shop_footer theme_settings={@theme_settings} mode={@mode} categories={@categories} /> <.cart_drawer cart_items={@cart_items} @@ -405,6 +406,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do """ attr :theme_settings, :map, required: true attr :mode, :atom, default: :live + attr :categories, :list, default: [] def shop_footer(assigns) do assigns = assign(assigns, :current_year, Date.utc_today().year) @@ -437,28 +439,19 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do All products -
  • - - New arrivals - -
  • -
  • - - Best sellers - -
  • + <%= for category <- @categories do %> +
  • + + {category.name} + +
  • + <% end %> <% else %>
  • -
  • - - New arrivals - -
  • -
  • - - Best sellers - -
  • + <%= for category <- @categories do %> +
  • + + {category.name} + +
  • + <% end %> <% end %> @@ -594,48 +580,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do

    © {@current_year} {@theme_settings.site_name}

    - + <.social_links /> diff --git a/lib/simpleshop_theme_web/components/shop_components/product.ex b/lib/simpleshop_theme_web/components/shop_components/product.ex index b6a2dfc..4a99d1f 100644 --- a/lib/simpleshop_theme_web/components/shop_components/product.ex +++ b/lib/simpleshop_theme_web/components/shop_components/product.ex @@ -59,58 +59,22 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do end) ~H""" - <%= if @clickable_resolved do %> - <%= if @mode == :preview do %> - - <.product_card_inner - product={@product} - theme_settings={@theme_settings} - variant={@variant} - priority={@priority} - show_category={@show_category_resolved} - show_badges={@show_badges_resolved} - show_delivery_text={@show_delivery_text_resolved} - /> - - <% else %> - - <.product_card_inner - product={@product} - theme_settings={@theme_settings} - variant={@variant} - priority={@priority} - show_category={@show_category_resolved} - show_badges={@show_badges_resolved} - show_delivery_text={@show_delivery_text_resolved} - /> - - <% end %> - <% else %> -
    - <.product_card_inner - product={@product} - theme_settings={@theme_settings} - variant={@variant} - priority={@priority} - show_category={@show_category_resolved} - show_badges={@show_badges_resolved} - show_delivery_text={@show_delivery_text_resolved} - /> -
    - <% end %> +
    if(@clickable_resolved, do: " position: relative;", else: "")} + > + <.product_card_inner + product={@product} + theme_settings={@theme_settings} + variant={@variant} + priority={@priority} + show_category={@show_category_resolved} + show_badges={@show_badges_resolved} + show_delivery_text={@show_delivery_text_resolved} + clickable={@clickable_resolved} + mode={@mode} + /> +
    """ end @@ -121,6 +85,8 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do attr :show_category, :boolean, required: true attr :show_badges, :boolean, required: true attr :show_delivery_text, :boolean, required: true + attr :clickable, :boolean, default: true + attr :mode, :atom, default: :live defp product_card_inner(assigns) do assigns = @@ -171,12 +137,47 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
    <%= if @show_category && @product[:category] do %> -

    - {@product.category} -

    + <%= if @mode == :preview do %> +

    + {@product.category} +

    + <% else %> + + {@product.category} + + <% end %> <% end %>

    - {@product.name} + <%= if @clickable do %> + <%= if @mode == :preview do %> + + {@product.name} + + <% else %> + + {@product.name} + + <% end %> + <% else %> + {@product.name} + <% end %>

    <%= if @theme_settings.show_prices do %> <.product_price product={@product} variant={@variant} />