defmodule PhoenixKitProjects.Web.TemplatesLive do @moduledoc "List project templates." use PhoenixKitWeb, :live_view use Gettext, backend: PhoenixKitProjects.Gettext use PhoenixKitProjects.Web.Components alias PhoenixKitProjects.{Activity, L10n, Paths, Projects} alias PhoenixKitProjects.PubSub, as: ProjectsPubSub alias PhoenixKitProjects.Schemas.Project alias PhoenixKitProjects.Web.Helpers, as: WebHelpers require Logger # Default wrapper class for the standalone admin page. Embedders can # override via `live_render(... session: %{"wrapper_class" => "..."})`. @default_wrapper_class "flex flex-col w-full px-4 py-6 gap-4" @impl true def mount(_params, session, socket) do WebHelpers.maybe_put_locale(session) if connected?(socket), do: ProjectsPubSub.subscribe(ProjectsPubSub.topic_templates()) wrapper_class = Map.get(session, "wrapper_class", @default_wrapper_class) socket = socket |> assign( page_title: gettext("Project Templates"), wrapper_class: wrapper_class, templates: [] ) |> WebHelpers.assign_embed_state(session) |> WebHelpers.attach_open_embed_hook() # Load on both disconnected + connected mount so the first paint has # real content. `handle_params/3` is intentionally absent — see # dev_docs/embedding_audit.md. {:ok, load_templates(socket)} end defp load_templates(socket), do: assign(socket, templates: Projects.list_templates()) @impl true def handle_info({:projects, _event, _payload}, socket) do {:noreply, load_templates(socket)} end def handle_info(msg, socket) do Logger.debug("[TemplatesLive] unexpected handle_info: #{inspect(msg)}") {:noreply, socket} end @impl true def handle_event("reorder_templates", %{"ordered_ids" => ordered_ids} = params, socket) when is_list(ordered_ids) do moved_id = params["moved_id"] case Projects.reorder_templates(ordered_ids, actor_uuid: Activity.actor_uuid(socket)) do :ok -> {:noreply, socket |> push_event("sortable:flash", %{uuid: moved_id, status: "ok"}) |> load_templates()} {:error, :too_many_uuids} -> {:noreply, socket |> put_flash(:error, gettext("Too many templates to reorder at once.")) |> push_event("sortable:flash", %{uuid: moved_id, status: "error"}) |> load_templates()} {:error, :wrong_scope} -> {:noreply, socket |> put_flash(:error, gettext("Template list changed; please try again.")) |> push_event("sortable:flash", %{uuid: moved_id, status: "error"}) |> load_templates()} end end def handle_event("delete", %{"uuid" => uuid}, socket) do case Projects.get_project(uuid) do nil -> {:noreply, put_flash(socket, :error, gettext("Template not found."))} template -> case Projects.delete_project(template) do {:ok, _} -> Activity.log("projects.template_deleted", actor_uuid: Activity.actor_uuid(socket), resource_type: "project_template", resource_uuid: template.uuid, metadata: %{"name" => template.name} ) {:noreply, socket |> WebHelpers.notify_deleted(:template, template.uuid) |> put_flash(:info, gettext("Template deleted.")) |> load_templates()} {:error, _} -> Activity.log_failed("projects.template_deleted", actor_uuid: Activity.actor_uuid(socket), resource_type: "project_template", resource_uuid: template.uuid, metadata: %{"name" => template.name} ) {:noreply, put_flash(socket, :error, gettext("Could not delete template."))} end end end @impl true def render(assigns) do ~H"""
<.page_header title={gettext("Project Templates")} description={gettext("Blueprint projects that can be cloned.")} > <:actions> <.smart_link navigate={Paths.new_template()} emit={{PhoenixKitProjects.Web.TemplateFormLive, %{"live_action" => "new"}}} embed_mode={@embed_mode} class="btn btn-primary btn-sm" > <.icon name="hero-plus" class="w-4 h-4" /> {gettext("New template")} <%= if @templates == [] do %> <.empty_state icon="hero-document-duplicate" title={gettext("No templates yet.")}> <:cta> <.smart_link navigate={Paths.new_template()} emit={{PhoenixKitProjects.Web.TemplateFormLive, %{"live_action" => "new"}}} embed_mode={@embed_mode} class="link link-primary text-sm" > {gettext("Create your first")} <% else %> <% lang = L10n.current_content_lang() %> <.sortable_table id="templates-list-body" rows={@templates} row_id={& &1.uuid} event="reorder_templates" > <:col :let={t} label={gettext("Name")}> <.smart_link navigate={Paths.template(t.uuid)} emit={{PhoenixKitProjects.Web.ProjectShowLive, %{"id" => t.uuid}}} embed_mode={@embed_mode} class="link link-hover font-medium" > {Project.localized_name(t, lang)} <% desc = Project.localized_description(t, lang) %>
{desc}
<:col :let={t} label={gettext("Weekends")}> {if t.counts_weekends, do: gettext("yes"), else: gettext("no")} <:col :let={t} label={gettext("Actions")} class="text-right"> <.table_row_menu id={"template-menu-#{t.uuid}"}> <.smart_menu_link navigate={Paths.edit_template(t.uuid)} emit={{PhoenixKitProjects.Web.TemplateFormLive, %{"live_action" => "edit", "id" => t.uuid}}} embed_mode={@embed_mode} icon="hero-pencil" label={gettext("Edit")} /> <.table_row_menu_divider /> <.table_row_menu_button phx-click="delete" phx-value-uuid={t.uuid} phx-disable-with={gettext("Deleting…")} data-confirm={gettext("Delete template \"%{name}\"?", name: Project.localized_name(t, lang))} icon="hero-trash" label={gettext("Delete")} variant="error" /> <% end %>
""" end end