berrypod/lib/berrypod_web/controllers/cart_controller.ex
jamey 0b0adba0fe
All checks were successful
deploy / deploy (push) Successful in 1m23s
add no-JS progressive enhancement for all shop flows
Every key shop flow now works via plain HTML forms when JS is
unavailable. LiveView progressively enhances when JS connects.

- PDP: form wraps variant/qty/add-to-cart with action="/cart/add"
- Cart page: qty +/- and remove use form POST fallbacks
- Cart/search header icons are now links with phx-click enhancement
- Collection sort form has GET action + noscript submit button
- New /search page with form-based search for no-JS users
- CartController gains add/remove/update_item POST actions
- CartHook gains update_quantity_form and remove_item_form handlers
- Fix flaky analytics tests caused by event table pollution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 22:56:19 +00:00

96 lines
2.4 KiB
Elixir

defmodule BerrypodWeb.CartController do
@moduledoc """
Cart controller handling both JSON API persistence and HTML form fallbacks.
The JSON `update/2` action is called by a JS hook after each LiveView cart
change. The HTML actions (`add/2`, `remove/2`, `update_item/2`) provide
no-JS fallbacks via plain form POST + redirect.
"""
use BerrypodWeb, :controller
alias Berrypod.Cart
@doc """
Updates the cart in session (JSON API for JS hook).
"""
def update(conn, %{"items" => items}) when is_list(items) do
cart_items = Cart.deserialize(items)
conn
|> Cart.put_in_session(cart_items)
|> json(%{ok: true})
end
def update(conn, _params) do
conn
|> put_status(:bad_request)
|> json(%{error: "Invalid cart data"})
end
@doc """
Adds an item to cart via form POST (no-JS fallback).
"""
def add(conn, %{"variant_id" => variant_id, "quantity" => qty_str}) do
quantity = parse_quantity(qty_str)
cart = Cart.get_from_session(get_session(conn))
cart = Cart.add_item(cart, variant_id, quantity)
conn
|> Cart.put_in_session(cart)
|> put_flash(:info, "Added to basket")
|> redirect(to: ~p"/cart")
end
def add(conn, _params) do
conn
|> put_flash(:error, "Could not add item to basket")
|> redirect(to: ~p"/cart")
end
@doc """
Removes an item from cart via form POST (no-JS fallback).
"""
def remove(conn, %{"variant_id" => variant_id}) do
cart = Cart.get_from_session(get_session(conn))
cart = Cart.remove_item(cart, variant_id)
conn
|> Cart.put_in_session(cart)
|> put_flash(:info, "Removed from basket")
|> redirect(to: ~p"/cart")
end
@doc """
Updates item quantity via form POST (no-JS fallback).
"""
def update_item(conn, %{"variant_id" => variant_id, "quantity" => qty_str}) do
quantity = parse_update_quantity(qty_str)
cart = Cart.get_from_session(get_session(conn))
cart = Cart.update_quantity(cart, variant_id, quantity)
conn
|> Cart.put_in_session(cart)
|> redirect(to: ~p"/cart")
end
defp parse_quantity(str) when is_binary(str) do
case Integer.parse(str) do
{qty, _} when qty > 0 -> qty
_ -> 1
end
end
defp parse_quantity(_), do: 1
# Allows 0 and negative values so Cart.update_quantity can remove items
defp parse_update_quantity(str) when is_binary(str) do
case Integer.parse(str) do
{qty, _} -> qty
:error -> 1
end
end
defp parse_update_quantity(_), do: 1
end