add quantity controls to cart drawer via shared CartHook

Move increment/decrement handlers from Cart LiveView into CartHook so
they work from any page's drawer. Enable show_quantity_controls on the
drawer's cart_item_row. Scope cart tests to #main-content to avoid
duplicate button matches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey 2026-02-11 00:15:04 +00:00
parent 3c73b98d2b
commit b3d1019cd4
5 changed files with 32 additions and 33 deletions

View File

@ -48,7 +48,7 @@ Issues found during hands-on testing of the deployed prod site on mobile and des
- [ ] Real product variants need testing and refinement with live data - [ ] Real product variants need testing and refinement with live data
### Cart ### Cart
- [ ] Should be able to change quantity in the cart drawer (currently only on cart page?) - [x] Should be able to change quantity in the cart drawer (currently only on cart page?)
- [ ] Cart drawer button → "view basket" feels redundant — streamline the flow - [ ] Cart drawer button → "view basket" feels redundant — streamline the flow
### Navigation & links ### Navigation & links

View File

@ -8,10 +8,11 @@ defmodule SimpleshopThemeWeb.CartHook do
Handles these events so individual LiveViews don't have to: Handles these events so individual LiveViews don't have to:
- `open_cart_drawer` / `close_cart_drawer` - toggle drawer visibility - `open_cart_drawer` / `close_cart_drawer` - toggle drawer visibility
- `remove_item` - remove item from cart - `remove_item` - remove item from cart
- `increment` / `decrement` - change item quantity
- `{:cart_updated, cart}` info - cross-tab cart sync via PubSub - `{:cart_updated, cart}` info - cross-tab cart sync via PubSub
LiveViews with custom cart logic (add_to_cart, increment, decrement) LiveViews with custom cart logic (e.g. add_to_cart) can call
can call `update_cart_assigns/2` and `broadcast_and_update/2` directly. `update_cart_assigns/2` and `broadcast_and_update/2` directly.
""" """
import Phoenix.Component, only: [assign: 3] import Phoenix.Component, only: [assign: 3]
@ -64,6 +65,31 @@ defmodule SimpleshopThemeWeb.CartHook do
{:halt, socket} {:halt, socket}
end end
defp handle_cart_event("increment", %{"id" => variant_id}, socket) do
cart = Cart.add_item(socket.assigns.raw_cart, variant_id, 1)
new_qty = Cart.get_quantity(cart, variant_id)
socket =
socket
|> broadcast_and_update(cart)
|> assign(:cart_status, "Quantity updated to #{new_qty}")
{:halt, socket}
end
defp handle_cart_event("decrement", %{"id" => variant_id}, socket) do
current = Cart.get_quantity(socket.assigns.raw_cart, variant_id)
cart = Cart.update_quantity(socket.assigns.raw_cart, variant_id, current - 1)
new_qty = Cart.get_quantity(cart, variant_id)
socket =
socket
|> broadcast_and_update(cart)
|> assign(:cart_status, "Quantity updated to #{new_qty}")
{:halt, socket}
end
defp handle_cart_event(_event, _params, socket), do: {:cont, socket} defp handle_cart_event(_event, _params, socket), do: {:cont, socket}
# Shared info handlers # Shared info handlers

View File

@ -105,7 +105,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
<ul role="list" aria-label="Cart items" style="list-style: none; margin: 0; padding: 0;"> <ul role="list" aria-label="Cart items" style="list-style: none; margin: 0; padding: 0;">
<%= for item <- @cart_items do %> <%= for item <- @cart_items do %>
<li style="border-bottom: 1px solid var(--t-border-default);"> <li style="border-bottom: 1px solid var(--t-border-default);">
<.cart_item_row item={item} size={:compact} mode={@mode} /> <.cart_item_row item={item} size={:compact} show_quantity_controls mode={@mode} />
</li> </li>
<% end %> <% end %>
</ul> </ul>

View File

@ -8,33 +8,6 @@ defmodule SimpleshopThemeWeb.ShopLive.Cart do
{:ok, assign(socket, :page_title, "Cart")} {:ok, assign(socket, :page_title, "Cart")}
end end
@impl true
def handle_event("increment", %{"id" => variant_id}, socket) do
cart = Cart.add_item(socket.assigns.raw_cart, variant_id, 1)
new_qty = Cart.get_quantity(cart, variant_id)
socket =
socket
|> SimpleshopThemeWeb.CartHook.broadcast_and_update(cart)
|> assign(:cart_status, "Quantity updated to #{new_qty}")
{:noreply, socket}
end
@impl true
def handle_event("decrement", %{"id" => variant_id}, socket) do
current = Cart.get_quantity(socket.assigns.raw_cart, variant_id)
cart = Cart.update_quantity(socket.assigns.raw_cart, variant_id, current - 1)
new_qty = Cart.get_quantity(cart, variant_id)
socket =
socket
|> SimpleshopThemeWeb.CartHook.broadcast_and_update(cart)
|> assign(:cart_status, "Quantity updated to #{new_qty}")
{:noreply, socket}
end
@impl true @impl true
def render(assigns) do def render(assigns) do
assigns = assign(assigns, :cart_page_subtotal, Cart.calculate_subtotal(assigns.cart_items)) assigns = assign(assigns, :cart_page_subtotal, Cart.calculate_subtotal(assigns.cart_items))

View File

@ -68,7 +68,7 @@ defmodule SimpleshopThemeWeb.ShopLive.CartTest do
html = html =
view view
|> element("button[aria-label='Increase quantity of #{product.title}']") |> element("#main-content button[aria-label='Increase quantity of #{product.title}']")
|> render_click() |> render_click()
assert html =~ "Quantity updated to 2" assert html =~ "Quantity updated to 2"
@ -83,7 +83,7 @@ defmodule SimpleshopThemeWeb.ShopLive.CartTest do
html = html =
view view
|> element("button[aria-label='Decrease quantity of #{product.title}']") |> element("#main-content button[aria-label='Decrease quantity of #{product.title}']")
|> render_click() |> render_click()
assert html =~ "Your basket is empty" assert html =~ "Your basket is empty"