defmodule BerrypodWeb.Admin.ProductShow do use BerrypodWeb, :live_view alias Berrypod.Products alias Berrypod.Products.{Product, ProductImage, ProductVariant} alias Berrypod.Cart @impl true def mount(%{"id" => id}, _session, socket) do case Products.get_product_with_preloads(id) do nil -> socket = socket |> put_flash(:error, "Product not found") |> push_navigate(to: ~p"/admin/products") {:ok, socket} product -> form = product |> Product.storefront_changeset(%{}) |> to_form(as: "product") socket = socket |> assign(:page_title, product.title) |> assign(:product, product) |> assign(:form, form) |> assign(:save_status, :idle) {:ok, socket} end end @impl true def handle_event("validate_storefront", %{"product" => params}, socket) do form = socket.assigns.product |> Product.storefront_changeset(params) |> Map.put(:action, :validate) |> to_form(as: "product") {:noreply, assign(socket, form: form, save_status: :idle)} end @impl true def handle_event("save_storefront", %{"product" => params}, socket) do case Products.update_storefront(socket.assigns.product, params) do {:ok, updated} -> product = %{ updated | provider_connection: socket.assigns.product.provider_connection, images: socket.assigns.product.images, variants: socket.assigns.product.variants } form = product |> Product.storefront_changeset(%{}) |> to_form(as: "product") socket = socket |> assign(:product, product) |> assign(:form, form) |> assign(:save_status, :saved) {:noreply, socket} {:error, changeset} -> {:noreply, assign(socket, form: to_form(changeset, as: "product"), save_status: :error)} end end @impl true def handle_event("resync", _params, socket) do product = socket.assigns.product if product.provider_connection do Products.enqueue_sync(product.provider_connection) {:noreply, put_flash(socket, :info, "Sync queued for #{product.provider_connection.name}")} else {:noreply, put_flash(socket, :error, "No provider connection")} end end @impl true def render(assigns) do ~H""" <.header> <.link navigate={~p"/admin/products"} class="admin-back-link"> ← Products
{@product.title} <.visibility_badge visible={@product.visible} /> <.status_badge status={@product.status} />
<:actions> <.external_link :if={provider_edit_url(@product)} href={provider_edit_url(@product)} icon={false} class="admin-btn admin-btn-ghost admin-btn-sm" > Edit on {provider_label(@product)} <.icon name="hero-arrow-top-right-on-square-mini" class="size-4" /> <.link navigate={~p"/products/#{@product.slug}"} class="admin-btn admin-btn-ghost admin-btn-sm" > View on shop <.icon name="hero-arrow-top-right-on-square-mini" class="size-4" /> <%!-- images + details --%>
{image.alt

No images

Details

<.list> <:item :if={@product.provider_connection} title="Provider"> {provider_label(@product)} via {@product.provider_connection.name} <:item title="Category">{@product.category || "—"} <:item title="Price">{Cart.format_price(@product.cheapest_price)} <:item title="Variants">{length(@product.variants)} <:item title="Images">{length(@product.images)} <:item title="Created">{format_date(@product.inserted_at)} <:item :if={@product.provider_connection && @product.provider_connection.last_synced_at} title="Last synced" > {format_date(@product.provider_connection.last_synced_at)}
<%!-- storefront controls --%>

Storefront controls

<.form for={@form} phx-submit="save_storefront" phx-change="validate_storefront" class="admin-filter-row-end" > <.button type="submit" variant="primary" class="admin-btn-sm">Save <.inline_feedback status={@save_status} />
<%!-- variants --%>

Variants ({length(@product.variants)})

<.table id="variants" rows={@product.variants}> <:col :let={variant} label="Options">{ProductVariant.options_title(variant)} <:col :let={variant} label="SKU">{variant.sku || "—"} <:col :let={variant} label="Price">{Cart.format_price(variant.price)} <:col :let={variant} label="Cost"> {if variant.cost, do: Cart.format_price(variant.cost), else: "—"} <:col :let={variant} label="Profit"> {if ProductVariant.profit(variant), do: Cart.format_price(ProductVariant.profit(variant)), else: "—"} <:col :let={variant} label="Available"> <.icon name="hero-check-circle-mini" class="size-5" /> <.icon name="hero-x-circle-mini" class="size-5" />
<%!-- provider data --%>

Provider data

<.list> <:item title="Provider"> {provider_label(@product)} via {@product.provider_connection.name} <:item title="Provider product ID">{@product.provider_product_id} <:item title="Status">{@product.status} <:item title="Sync status">{@product.provider_connection.sync_status}
""" end # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- defp sorted_images(product) do (product.images || []) |> Enum.sort_by(& &1.position) end defp visibility_badge(assigns) do {color, label} = if assigns.visible do {"green", "visible"} else {"zinc", "hidden"} end assigns = assign(assigns, color: color, label: label) ~H""" {@label} """ end defp status_badge(assigns) do color = case assigns.status do "active" -> "green" "draft" -> "amber" _ -> "zinc" end assigns = assign(assigns, :color, color) ~H""" {@status} """ end defp provider_label(%{provider_connection: %{provider_type: "printify"}}), do: "Printify" defp provider_label(%{provider_connection: %{provider_type: "printful"}}), do: "Printful" defp provider_label(%{provider_connection: %{provider_type: type}}), do: String.capitalize(type) defp provider_label(_), do: nil defp provider_edit_url(%{ provider_connection: %{provider_type: "printify", config: config}, provider_product_id: pid }) do shop_id = config["shop_id"] if shop_id && pid, do: "https://printify.com/app/editor/#{shop_id}/#{pid}" end defp provider_edit_url(%{ provider_connection: %{provider_type: "printful"}, provider_product_id: pid }) do if pid, do: "https://www.printful.com/dashboard/sync/update?id=#{pid}" end defp provider_edit_url(_), do: nil defp format_date(nil), do: "—" defp format_date(datetime), do: Calendar.strftime(datetime, "%d %b %Y %H:%M") end