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 - `{:cart_updated, cart}` info - cross-tab cart sync via PubSub LiveViews with custom cart logic (add_to_cart, increment, decrement) 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(_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