shop_footer now calls PreviewData.categories() itself, removing the need to thread categories through CartHook, shop_layout, and all 8 page templates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
138 lines
4.0 KiB
Elixir
138 lines
4.0 KiB
Elixir
defmodule SimpleshopThemeWeb.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
|
|
- `{: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 SimpleshopTheme.Cart
|
|
|
|
def on_mount(:mount_cart, _params, session, socket) do
|
|
cart_items = Cart.get_from_session(session)
|
|
|
|
socket =
|
|
socket
|
|
|> 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(SimpleshopTheme.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("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)
|
|
|
|
socket
|
|
|> assign(:raw_cart, cart)
|
|
|> assign(:cart_items, items)
|
|
|> assign(:cart_count, count)
|
|
|> assign(:cart_subtotal, subtotal)
|
|
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(
|
|
SimpleshopTheme.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
|