rename project from SimpleshopTheme to Berrypod
All modules, configs, paths, and references updated. 836 tests pass, zero warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
166
lib/berrypod_web/cart_hook.ex
Normal file
166
lib/berrypod_web/cart_hook.ex
Normal file
@@ -0,0 +1,166 @@
|
||||
defmodule BerrypodWeb.CartHook do
|
||||
@moduledoc """
|
||||
LiveView on_mount hook for cart state and shared event handling.
|
||||
|
||||
Mounted in the public_shop live_session to give all shop LiveViews
|
||||
cart state, PubSub sync, and shared event handlers via attach_hook.
|
||||
|
||||
Handles these events so individual LiveViews don't have to:
|
||||
- `open_cart_drawer` / `close_cart_drawer` - toggle drawer visibility
|
||||
- `remove_item` - remove item from cart
|
||||
- `increment` / `decrement` - change item quantity
|
||||
- `change_country` - update shipping country
|
||||
- `{:cart_updated, cart}` info - cross-tab cart sync via PubSub
|
||||
|
||||
LiveViews with custom cart logic (e.g. add_to_cart) can call
|
||||
`update_cart_assigns/2` and `broadcast_and_update/2` directly.
|
||||
"""
|
||||
|
||||
import Phoenix.Component, only: [assign: 3]
|
||||
import Phoenix.LiveView, only: [attach_hook: 4, connected?: 1, push_event: 3]
|
||||
|
||||
alias Berrypod.Cart
|
||||
alias Berrypod.Shipping
|
||||
|
||||
def on_mount(:mount_cart, _params, session, socket) do
|
||||
cart_items = Cart.get_from_session(session)
|
||||
country_code = session["country_code"] || "GB"
|
||||
available_countries = Shipping.list_available_countries_with_names()
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:country_code, country_code)
|
||||
|> assign(:available_countries, available_countries)
|
||||
|> update_cart_assigns(cart_items)
|
||||
|> assign(:cart_drawer_open, false)
|
||||
|> assign(:cart_status, nil)
|
||||
|> attach_hook(:cart_events, :handle_event, &handle_cart_event/3)
|
||||
|> attach_hook(:cart_info, :handle_info, &handle_cart_info/2)
|
||||
|
||||
socket =
|
||||
if connected?(socket) do
|
||||
csrf_token = Map.get(session, "_csrf_token", "default")
|
||||
topic = "cart:#{csrf_token}"
|
||||
Phoenix.PubSub.subscribe(Berrypod.PubSub, topic)
|
||||
assign(socket, :cart_topic, topic)
|
||||
else
|
||||
assign(socket, :cart_topic, nil)
|
||||
end
|
||||
|
||||
{:cont, socket}
|
||||
end
|
||||
|
||||
# Shared event handlers
|
||||
|
||||
defp handle_cart_event("open_cart_drawer", _params, socket) do
|
||||
{:halt, assign(socket, :cart_drawer_open, true)}
|
||||
end
|
||||
|
||||
defp handle_cart_event("close_cart_drawer", _params, socket) do
|
||||
{:halt, assign(socket, :cart_drawer_open, false)}
|
||||
end
|
||||
|
||||
defp handle_cart_event("change_country", %{"country" => code}, socket) do
|
||||
socket =
|
||||
socket
|
||||
|> assign(:country_code, code)
|
||||
|> update_cart_assigns(socket.assigns.raw_cart)
|
||||
|> push_event("persist_country", %{code: code})
|
||||
|
||||
{:halt, socket}
|
||||
end
|
||||
|
||||
defp handle_cart_event("remove_item", %{"id" => variant_id}, socket) do
|
||||
cart = Cart.remove_item(socket.assigns.raw_cart, variant_id)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> broadcast_and_update(cart)
|
||||
|> assign(:cart_status, "Item removed from cart")
|
||||
|
||||
{:halt, socket}
|
||||
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}
|
||||
|
||||
# Shared info handlers
|
||||
|
||||
defp handle_cart_info({:cart_updated, cart}, socket) do
|
||||
{:halt, update_cart_assigns(socket, cart)}
|
||||
end
|
||||
|
||||
defp handle_cart_info(_msg, socket), do: {:cont, socket}
|
||||
|
||||
# Public helpers for LiveViews with custom cart logic
|
||||
|
||||
@doc """
|
||||
Updates all cart-related assigns from raw cart data.
|
||||
"""
|
||||
def update_cart_assigns(socket, cart) do
|
||||
%{items: items, count: count, subtotal: subtotal} = Cart.build_state(cart)
|
||||
country_code = socket.assigns[:country_code] || "GB"
|
||||
subtotal_pence = Cart.calculate_subtotal(items)
|
||||
|
||||
shipping_estimate =
|
||||
case Shipping.calculate_for_cart(items, country_code) do
|
||||
{:ok, cost} when cost > 0 -> cost
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
cart_total =
|
||||
Cart.format_price(subtotal_pence + (shipping_estimate || 0))
|
||||
|
||||
socket
|
||||
|> assign(:raw_cart, cart)
|
||||
|> assign(:cart_items, items)
|
||||
|> assign(:cart_count, count)
|
||||
|> assign(:cart_subtotal, subtotal)
|
||||
|> assign(:cart_total, cart_total)
|
||||
|> assign(:shipping_estimate, shipping_estimate)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Broadcasts cart update to other tabs and updates local assigns.
|
||||
|
||||
Uses broadcast_from to avoid notifying self (prevents double-update).
|
||||
"""
|
||||
def broadcast_and_update(socket, cart) do
|
||||
if socket.assigns.cart_topic do
|
||||
Phoenix.PubSub.broadcast_from(
|
||||
Berrypod.PubSub,
|
||||
self(),
|
||||
socket.assigns.cart_topic,
|
||||
{:cart_updated, cart}
|
||||
)
|
||||
end
|
||||
|
||||
socket
|
||||
|> update_cart_assigns(cart)
|
||||
|> push_event("persist_cart", %{items: Cart.serialize(cart)})
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user