defmodule BerrypodWeb.ShopComponents.Cart do @moduledoc false use Phoenix.Component import BerrypodWeb.ShopComponents.Base alias Berrypod.Products.{Product, ProductImage} defp close_cart_drawer_js do Phoenix.LiveView.JS.push("close_cart_drawer") end @doc """ Renders the cart drawer (floating sidebar). The drawer slides in from the right when opened. It displays cart items and checkout options. Follows WAI-ARIA dialog pattern for accessibility. ## Attributes * `cart_items` - List of cart items to display. Each item should have `image`, `name`, `variant`, `price`, and `variant_id` keys. Default: [] * `subtotal` - The subtotal to display. Default: nil (shows "£0.00") * `cart_count` - Number of items for screen reader description. Default: 0 * `mode` - Either `:live` (default) for real stores or `:preview` for theme editor. In preview mode, "View basket" navigates via LiveView JS commands. ## Examples <.cart_drawer cart_items={@cart.items} subtotal={@cart.subtotal} /> <.cart_drawer cart_items={demo_items} subtotal="£72.00" mode={:preview} /> """ attr :cart_items, :list, default: [] attr :subtotal, :string, default: nil attr :total, :string, default: nil attr :cart_count, :integer, default: 0 attr :cart_status, :string, default: nil attr :mode, :atom, default: :live attr :open, :boolean, default: false attr :shipping_estimate, :integer, default: nil attr :country_code, :string, default: "GB" attr :available_countries, :list, default: [] def cart_drawer(assigns) do assigns = assign_new(assigns, :display_total, fn -> assigns.total || assigns.subtotal || "£0.00" end) ~H""" <%!-- Screen reader announcements for cart changes --%>
{@cart_status}
""" end @doc """ Shared cart item row component used by both drawer and cart page. ## Attributes * `item` - Required. Cart item with `name`, `variant`, `price`, `quantity`, `image`, `variant_id`, `product_id`. * `size` - Either `:compact` (drawer) or `:default` (cart page). Default: :default * `show_quantity_controls` - Show +/- buttons. Default: false * `mode` - Either `:live` or `:preview`. Default: :live """ attr :item, :map, required: true attr :size, :atom, default: :default attr :show_quantity_controls, :boolean, default: false attr :mode, :atom, default: :live def cart_item_row(assigns) do ~H"""
<%= if @mode != :preview do %> <.link navigate={"/products/#{@item.product_id}"} class={["cart-item-image", !@item.image && "cart-item-image--empty"]} data-size={if @size == :compact, do: "compact"} style={if @item.image, do: "background-image: url('#{@item.image}');"} aria-label={"View #{@item.name}"} > <% else %>
<% end %>

<%= if @mode != :preview do %> <.link navigate={"/products/#{@item.product_id}"} class="cart-item-name-link" > {@item.name} <% else %> {@item.name} <% end %>

<%= if @item.variant do %>

{@item.variant}

<% end %>
<%= if @show_quantity_controls do %>
{@item.quantity}
<% else %> Qty: {@item.quantity} <% end %> <.cart_remove_button variant_id={@item.variant_id} item_name={@item.name} />

{Berrypod.Cart.format_price(@item.price * @item.quantity)}

""" end @doc """ Cart empty state component. """ attr :mode, :atom, default: :live def cart_empty_state(assigns) do ~H"""

Your basket is empty

<%= if @mode == :preview do %> <% else %> <.link navigate="/collections/all" class="cart-continue-link" > Continue shopping <% end %>
""" end @doc """ Remove button for cart items. """ attr :variant_id, :string, required: true attr :item_name, :string, default: "item" def cart_remove_button(assigns) do ~H"""
""" end @doc """ Renders a cart item row. ## Attributes * `item` - Required. Map with `product` (containing `image_url`, `name`, `price`), `variant`, and `quantity`. * `currency` - Optional. Currency symbol. Defaults to "£". ## Examples <.cart_item item={item} /> """ attr :item, :map, required: true def cart_item(assigns) do ~H""" <.shop_card class="cart-page-item">
{@item.product.title}

{@item.product.title}

{@item.variant}

{@item.quantity}

{Berrypod.Cart.format_price(@item.product.cheapest_price * @item.quantity)}

""" end defp cart_item_image(product) do ProductImage.url(Product.primary_image(product), 400) end # Shared delivery line used by both cart_drawer and order_summary. # Shows a country <% else %> {Berrypod.Shipping.country_name(@country_code)} <% end %> <%= if @shipping_estimate do %> {Berrypod.Cart.format_price(@shipping_estimate)} <% else %> Calculated at checkout <% end %> """ end @doc """ Renders the order summary card. ## Attributes * `subtotal` - Required. Subtotal amount (in pence/cents). * `shipping_estimate` - Optional. Shipping estimate in pence. * `country_code` - Optional. Current country code. Default "GB". * `available_countries` - Optional. List of `{code, name}` tuples. * `mode` - Either `:live` (default) or `:preview`. ## Examples <.order_summary subtotal={3600} /> """ attr :subtotal, :integer, required: true attr :shipping_estimate, :integer, default: nil attr :country_code, :string, default: "GB" attr :available_countries, :list, default: [] attr :mode, :atom, default: :live def order_summary(assigns) do assigns = assign(assigns, :estimated_total, assigns.subtotal + (assigns.shipping_estimate || 0)) ~H""" <.shop_card class="order-summary-card">

Order summary

Subtotal {Berrypod.Cart.format_price(@subtotal)}
<.delivery_line shipping_estimate={@shipping_estimate} country_code={@country_code} available_countries={@available_countries} mode={@mode} />
{if @shipping_estimate, do: "Estimated total", else: "Subtotal"} {Berrypod.Cart.format_price(@estimated_total)}
<%= if @mode == :preview do %> <.shop_button class="order-summary-checkout"> Checkout <.shop_button_outline phx-click="change_preview_page" phx-value-page="collection" class="order-summary-continue" > Continue shopping <% else %>
<.shop_button type="submit" class="order-summary-checkout"> Checkout
<.shop_link_outline href="/collections/all" class="order-summary-continue" > Continue shopping <% end %> """ end @doc """ Renders a cart items list with order summary layout. ## Attributes * `items` - Required. List of cart items. * `subtotal` - Required. Subtotal in pence/cents. * `currency` - Optional. Currency symbol. Defaults to "£". * `mode` - Either `:live` (default) or `:preview`. ## Examples <.cart_layout items={@cart_items} subtotal={3600} mode={:preview} /> """ attr :items, :list, required: true attr :subtotal, :integer, required: true attr :mode, :atom, default: :live def cart_layout(assigns) do ~H"""
<%= for item <- @items do %> <.cart_item item={item} /> <% end %>
<.order_summary subtotal={@subtotal} mode={@mode} />
""" end end