defmodule PhoenixKit.Modules.Shop.Web.Settings do @moduledoc """ E-Commerce module settings LiveView. Allows configuration of e-commerce settings including inventory tracking. """ use PhoenixKitWeb, :live_view alias PhoenixKit.Modules.Billing alias PhoenixKit.Modules.Shop alias PhoenixKit.Settings alias PhoenixKit.Utils.Routes # Test comment for live reload @impl true def mount(_params, _session, socket) do config = Shop.get_config() # Load storefront filter configuration storefront_filters = Shop.get_storefront_filters() discovered_options = Shop.discover_filterable_options() socket = socket |> assign(:page_title, "E-Commerce Settings") |> assign(:enabled, config.enabled) |> assign(:inventory_tracking, config.inventory_tracking) |> assign(:billing_enabled, billing_enabled?()) |> assign(:category_name_display, get_category_name_display()) |> assign(:category_icon_mode, get_category_icon_mode()) |> assign(:sidebar_show_categories, get_sidebar_show_categories()) |> assign(:storefront_filters, storefront_filters) |> assign(:discovered_options, discovered_options) {:ok, socket} end defp get_category_name_display do Settings.get_setting_cached("shop_category_name_display", "truncate") end defp get_category_icon_mode do Settings.get_setting_cached("shop_category_icon_mode", "none") end defp get_sidebar_show_categories do Settings.get_setting_cached("shop_sidebar_show_categories", "true") == "true" end @impl true def handle_event("toggle_enabled", _params, socket) do new_enabled = !socket.assigns.enabled if new_enabled and not billing_enabled?() do # Cannot enable Shop without Billing {:noreply, socket |> assign(:enabled, false) |> put_flash(:error, "Please enable Billing module first")} else result = if new_enabled do Shop.enable_system() else Shop.disable_system() end case result do :ok -> socket = socket |> assign(:enabled, new_enabled) |> put_flash( :info, if(new_enabled, do: "E-Commerce module enabled", else: "E-Commerce module disabled") ) {:noreply, socket} _ -> {:noreply, socket |> assign(:enabled, false) |> put_flash(:error, "Failed to update E-Commerce status")} end end end @impl true def handle_event("toggle_inventory_tracking", _params, socket) do new_value = !socket.assigns.inventory_tracking value_str = if(new_value, do: "true", else: "false") case Settings.update_setting("shop_inventory_tracking", value_str) do {:ok, _} -> {:noreply, socket |> assign(:inventory_tracking, new_value) |> put_flash( :info, if(new_value, do: "Inventory tracking enabled", else: "Inventory tracking disabled") )} {:error, _} -> {:noreply, put_flash(socket, :error, "Failed to update inventory setting")} end end @impl true def handle_event("update_category_display", %{"display" => display}, socket) do case Settings.update_setting("shop_category_name_display", display) do {:ok, _} -> {:noreply, socket |> assign(:category_name_display, display) |> put_flash(:info, "Category display setting updated")} {:error, _} -> {:noreply, put_flash(socket, :error, "Failed to update category display setting")} end end @impl true def handle_event("toggle_sidebar_categories", _params, socket) do new_value = !socket.assigns.sidebar_show_categories value_str = if(new_value, do: "true", else: "false") case Settings.update_setting("shop_sidebar_show_categories", value_str) do {:ok, _} -> {:noreply, socket |> assign(:sidebar_show_categories, new_value) |> put_flash( :info, if(new_value, do: "Categories in shop enabled", else: "Categories in shop disabled" ) )} {:error, _} -> {:noreply, put_flash(socket, :error, "Failed to update setting")} end end @impl true def handle_event("update_category_icon", %{"mode" => mode}, socket) do case Settings.update_setting("shop_category_icon_mode", mode) do {:ok, _} -> {:noreply, socket |> assign(:category_icon_mode, mode) |> put_flash(:info, "Category icon setting updated")} {:error, _} -> {:noreply, put_flash(socket, :error, "Failed to update category icon setting")} end end @impl true def handle_event("toggle_storefront_filter", %{"key" => key}, socket) do filters = Enum.map(socket.assigns.storefront_filters, fn f -> if f["key"] == key, do: Map.put(f, "enabled", !f["enabled"]), else: f end) case Shop.update_storefront_filters(filters) do {:ok, _} -> {:noreply, socket |> assign(:storefront_filters, filters) |> put_flash(:info, "Storefront filter updated")} {:error, _} -> {:noreply, put_flash(socket, :error, "Failed to update filter")} end end @impl true def handle_event("update_filter_label", %{"key" => key, "label" => label}, socket) do filters = Enum.map(socket.assigns.storefront_filters, fn f -> if f["key"] == key, do: Map.put(f, "label", label), else: f end) case Shop.update_storefront_filters(filters) do {:ok, _} -> {:noreply, assign(socket, :storefront_filters, filters)} {:error, _} -> {:noreply, put_flash(socket, :error, "Failed to update filter label")} end end @impl true def handle_event("add_metadata_filter", %{"key" => option_key}, socket) do existing_keys = Enum.map(socket.assigns.storefront_filters, & &1["key"]) if option_key in existing_keys do {:noreply, put_flash(socket, :error, "Filter for '#{option_key}' already exists")} else max_pos = socket.assigns.storefront_filters |> Enum.map(& &1["position"]) |> Enum.max(fn -> 0 end) new_filter = %{ "key" => option_key, "type" => "metadata_option", "option_key" => option_key, "label" => String.capitalize(option_key), "enabled" => true, "position" => max_pos + 1 } filters = socket.assigns.storefront_filters ++ [new_filter] case Shop.update_storefront_filters(filters) do {:ok, _} -> {:noreply, socket |> assign(:storefront_filters, filters) |> put_flash(:info, "Filter '#{option_key}' added")} {:error, _} -> {:noreply, put_flash(socket, :error, "Failed to add filter")} end end end @impl true def handle_event("remove_filter", %{"key" => key}, socket) do filters = Enum.reject(socket.assigns.storefront_filters, &(&1["key"] == key)) case Shop.update_storefront_filters(filters) do {:ok, _} -> {:noreply, socket |> assign(:storefront_filters, filters) |> put_flash(:info, "Filter removed")} {:error, _} -> {:noreply, put_flash(socket, :error, "Failed to remove filter")} end end @impl true def handle_event("reset_default_filters", _params, socket) do filters = Shop.default_storefront_filters() case Shop.update_storefront_filters(filters) do {:ok, _} -> {:noreply, socket |> assign(:storefront_filters, filters) |> put_flash(:info, "Filters reset to defaults")} {:error, _} -> {:noreply, put_flash(socket, :error, "Failed to reset filters")} end end @impl true def render(assigns) do ~H"""
<%!-- Header --%>
<.link navigate={Routes.path("/admin/shop")} class="btn btn-ghost btn-sm" > <.icon name="hero-arrow-left" class="w-4 h-4" />

E-Commerce Settings

Configure your e-commerce store

<%!-- Module Status Card --%>

Module Status

Enable or disable the E-Commerce module

{if @enabled, do: "Enabled", else: "Disabled"}
<%= if not @billing_enabled do %> <.link navigate={Routes.path("/admin/modules")} class="badge badge-warning badge-sm hover:badge-error" > Billing Required <% end %>
<%!-- Inventory Settings (toggle pattern) --%>

<.icon name="hero-archive-box" class="w-6 h-6" /> Inventory

<%!-- Info about Billing --%>
<.icon name="hero-information-circle" class="w-6 h-6" />

Currency & Tax Settings

Currency and tax configuration is managed in the <.link navigate={Routes.path("/admin/settings/billing")} class="link font-medium"> Billing module settings

<%!-- Product Options --%>

<.icon name="hero-tag" class="w-6 h-6" /> Product Options

<%!-- Import Configurations --%>

<.icon name="hero-funnel" class="w-6 h-6" /> Import Configurations

<%!-- Storefront Filters --%>

<.icon name="hero-funnel" class="w-6 h-6" /> Storefront Filters

Configure product filters shown on the storefront sidebar. Customers can filter by price, vendor, and product options.

<%!-- Current Filters Table --%>
<%= for filter <- @storefront_filters do %> <% end %>
Filter Type Label Enabled
{filter["key"]} {filter["type"]}
<%= if filter["type"] == "metadata_option" do %> <% end %>
<%!-- Auto-discovered option keys --%> <%= if @discovered_options != [] do %>
Available Product Options

These option keys were found in product metadata. Click to add as a filter.

<% existing_keys = Enum.map(@storefront_filters, & &1["key"]) %> <%= for opt <- @discovered_options do %> <%= if opt.key not in existing_keys do %> <% end %> <% end %>
<% end %>
<%!-- Sidebar Display Settings --%>

<.icon name="hero-bars-3" class="w-6 h-6" /> Sidebar Display

<%!-- Show Categories in Shop --%>
<%!-- Category Name Display --%>

How category names should be displayed in the sidebar

<%!-- Category Icon Mode --%>

Show icons next to category names in sidebar

""" end defp billing_enabled? do Code.ensure_loaded?(Billing) and function_exported?(Billing, :enabled?, 0) and Billing.enabled?() rescue _ -> false end end