add no-JS fallback for cart country selector
All checks were successful
deploy / deploy (push) Successful in 59s

The delivery country form now has action="/cart/country" with a
noscript submit button. Without JS, changing the country and clicking
Update POSTs to a new CartController.update_country action that saves
the country to session and redirects back to /cart.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey 2026-02-24 23:14:48 +00:00
parent 79764c7766
commit d0ea9d59f5
5 changed files with 64 additions and 1 deletions

View File

@ -403,7 +403,8 @@ defmodule BerrypodWeb.ShopComponents.Cart do
<span class="delivery-line-label"> <span class="delivery-line-label">
Delivery to Delivery to
<%= if @available_countries != [] and @mode != :preview do %> <%= if @available_countries != [] and @mode != :preview do %>
<form phx-change="change_country"> <form action="/cart/country" method="post" phx-change="change_country">
<input type="hidden" name="_csrf_token" value={Phoenix.Controller.get_csrf_token()} />
<select <select
name="country" name="country"
class="delivery-select" class="delivery-select"
@ -413,6 +414,9 @@ defmodule BerrypodWeb.ShopComponents.Cart do
<option value={code} selected={code == @country_code}>{name}</option> <option value={code} selected={code == @country_code}>{name}</option>
<% end %> <% end %>
</select> </select>
<noscript>
<button type="submit" class="themed-button delivery-country-submit">Update</button>
</noscript>
</form> </form>
<% else %> <% else %>
<span>{Berrypod.Shipping.country_name(@country_code)}</span> <span>{Berrypod.Shipping.country_name(@country_code)}</span>

View File

@ -74,6 +74,19 @@ defmodule BerrypodWeb.CartController do
|> redirect(to: ~p"/cart") |> redirect(to: ~p"/cart")
end end
@doc """
Updates delivery country via form POST (no-JS fallback).
"""
def update_country(conn, %{"country" => code}) when is_binary(code) and code != "" do
conn
|> put_session("country_code", code)
|> redirect(to: ~p"/cart")
end
def update_country(conn, _params) do
redirect(conn, to: ~p"/cart")
end
defp parse_quantity(str) when is_binary(str) do defp parse_quantity(str) when is_binary(str) do
case Integer.parse(str) do case Integer.parse(str) do
{qty, _} when qty > 0 -> qty {qty, _} when qty > 0 -> qty

View File

@ -97,6 +97,7 @@ defmodule BerrypodWeb.Router do
post "/cart/add", CartController, :add post "/cart/add", CartController, :add
post "/cart/remove", CartController, :remove post "/cart/remove", CartController, :remove
post "/cart/update", CartController, :update_item post "/cart/update", CartController, :update_item
post "/cart/country", CartController, :update_country
end end
# Health check (no auth, no theme loading — for load balancers and uptime monitors) # Health check (no auth, no theme loading — for load balancers and uptime monitors)

View File

@ -108,4 +108,19 @@ defmodule BerrypodWeb.CartControllerTest do
assert cart == [] assert cart == []
end end
end end
describe "POST /cart/country" do
test "saves country to session and redirects to /cart", %{conn: conn} do
conn = post(conn, ~p"/cart/country", %{"country" => "DE"})
assert redirected_to(conn) == "/cart"
assert get_session(conn, "country_code") == "DE"
end
test "handles missing country param", %{conn: conn} do
conn = post(conn, ~p"/cart/country", %{})
assert redirected_to(conn) == "/cart"
end
end
end end

View File

@ -106,6 +106,36 @@ defmodule BerrypodWeb.Shop.CartTest do
end end
end end
describe "no-JS fallback" do
setup [:create_cart_with_product]
test "country selector form has action for no-JS submission", %{
conn: conn,
variant: variant
} do
# Insert a shipping rate so the country selector renders
now = DateTime.utc_now() |> DateTime.truncate(:second)
pc = Berrypod.ProductsFixtures.provider_connection_fixture()
Berrypod.Repo.insert_all(Berrypod.Shipping.ShippingRate, [
[
blueprint_id: 1,
print_provider_id: 1,
country_code: "GB",
first_item_cost: 399,
additional_item_cost: 100,
provider_connection_id: pc.id,
inserted_at: now,
updated_at: now
]
])
{:ok, view, _html} = conn |> conn_with_cart(variant.id) |> live(~p"/cart")
assert has_element?(view, "form[action='/cart/country'][method='post']")
end
end
describe "Cart page title" do describe "Cart page title" do
test "page title is Cart", %{conn: conn} do test "page title is Cart", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/cart") {:ok, _view, html} = live(conn, ~p"/cart")