diff --git a/lib/mix/tasks/fyi.install.ex b/lib/mix/tasks/fyi.install.ex index 16313a7..bdc5a41 100644 --- a/lib/mix/tasks/fyi.install.ex +++ b/lib/mix/tasks/fyi.install.ex @@ -1,367 +1,369 @@ -defmodule Mix.Tasks.Fyi.Install do - @shortdoc "Installs FYI into your Phoenix application" - @moduledoc """ - Installs FYI into your Phoenix application. - - $ mix fyi.install - - ## Options - - - `--no-ui` - Skip installing the Phoenix UI components - - `--no-persist` - Skip the database migration (events won't be persisted) - - `--no-feedback` - Skip installing the feedback component - - ## What This Does - - 1. Adds FYI.Application to your supervision tree - 2. Creates a migration for the `fyi_events` table (unless --no-persist) - 3. Adds a `/fyi` route scope to your router (unless --no-ui) - 4. Installs a customizable feedback component (unless --no-feedback) - 5. Adds configuration stubs to your config files - """ - - use Igniter.Mix.Task - - @impl Igniter.Mix.Task - def info(_argv, _parent) do - %Igniter.Mix.Task.Info{ - group: :fyi, - example: "mix fyi.install", - positional: [], - schema: [ - no_ui: :boolean, - no_persist: :boolean, - no_feedback: :boolean - ], - defaults: [ - no_ui: false, - no_persist: false, - no_feedback: false - ] - } - end +if Code.ensure_loaded?(Igniter.Mix.Task) do + defmodule Mix.Tasks.Fyi.Install do + @shortdoc "Installs FYI into your Phoenix application" + @moduledoc """ + Installs FYI into your Phoenix application. - @impl Igniter.Mix.Task - def igniter(igniter) do - opts = igniter.args.options - - igniter - |> validate_phoenix_app() - |> add_to_supervision_tree() - |> maybe_add_migration(opts) - |> maybe_add_routes(opts) - |> maybe_add_feedback_component(opts) - |> add_config() - end + $ mix fyi.install - defp validate_phoenix_app(igniter) do - # Check if Phoenix and Ecto are present - case Igniter.Project.Deps.get_dep(igniter, :phoenix) do - {:ok, _} -> - igniter + ## Options - :error -> - Igniter.add_warning(igniter, """ - FYI is designed for Phoenix applications. Some features may not work correctly - without Phoenix and Ecto. - """) + - `--no-ui` - Skip installing the Phoenix UI components + - `--no-persist` - Skip the database migration (events won't be persisted) + - `--no-feedback` - Skip installing the feedback component + + ## What This Does + + 1. Adds FYI.Application to your supervision tree + 2. Creates a migration for the `fyi_events` table (unless --no-persist) + 3. Adds a `/fyi` route scope to your router (unless --no-ui) + 4. Installs a customizable feedback component (unless --no-feedback) + 5. Adds configuration stubs to your config files + """ + + use Igniter.Mix.Task + + @impl Igniter.Mix.Task + def info(_argv, _parent) do + %Igniter.Mix.Task.Info{ + group: :fyi, + example: "mix fyi.install", + positional: [], + schema: [ + no_ui: :boolean, + no_persist: :boolean, + no_feedback: :boolean + ], + defaults: [ + no_ui: false, + no_persist: false, + no_feedback: false + ] + } end - end - defp add_to_supervision_tree(igniter) do - Igniter.Project.Application.add_new_child( - igniter, - FYI.Application, - after: [:repo, :pubsub, :endpoint] - ) - end + @impl Igniter.Mix.Task + def igniter(igniter) do + opts = igniter.args.options - defp maybe_add_migration(igniter, opts) do - if opts[:no_persist] do igniter - else - timestamp = Calendar.strftime(DateTime.utc_now(), "%Y%m%d%H%M%S") - filename = "#{timestamp}_create_fyi_events.exs" - - migration_content = """ - defmodule Repo.Migrations.CreateFyiEvents do - use Ecto.Migration - - def change do - create table(:fyi_events, primary_key: false) do - add :id, :string, primary_key: true - add :name, :string, null: false - add :payload, :map, default: %{} - add :tags, :map, default: %{} - add :actor, :string - add :source, :string - - timestamps(updated_at: false, type: :utc_datetime_usec) - end + |> validate_phoenix_app() + |> add_to_supervision_tree() + |> maybe_add_migration(opts) + |> maybe_add_routes(opts) + |> maybe_add_feedback_component(opts) + |> add_config() + end - create index(:fyi_events, [:name]) - create index(:fyi_events, [:actor]) - create index(:fyi_events, [:inserted_at]) - end + defp validate_phoenix_app(igniter) do + # Check if Phoenix and Ecto are present + case Igniter.Project.Deps.get_dep(igniter, :phoenix) do + {:ok, _} -> + igniter + + :error -> + Igniter.add_warning(igniter, """ + FYI is designed for Phoenix applications. Some features may not work correctly + without Phoenix and Ecto. + """) end - """ - - Igniter.create_new_file(igniter, "priv/repo/migrations/#{filename}", migration_content) end - end - defp maybe_add_routes(igniter, opts) do - if opts[:no_ui] do - igniter - else - route_code = - """ - # FYI Admin UI - scope "/fyi", FYI.Web do - pipe_through [:browser] + defp add_to_supervision_tree(igniter) do + Igniter.Project.Application.add_new_child( + igniter, + FYI.Application, + after: [:repo, :pubsub, :endpoint] + ) + end - live "/", InboxLive, :index - live "/events/:id", InboxLive, :show + defp maybe_add_migration(igniter, opts) do + if opts[:no_persist] do + igniter + else + timestamp = Calendar.strftime(DateTime.utc_now(), "%Y%m%d%H%M%S") + filename = "#{timestamp}_create_fyi_events.exs" + + migration_content = """ + defmodule Repo.Migrations.CreateFyiEvents do + use Ecto.Migration + + def change do + create table(:fyi_events, primary_key: false) do + add :id, :string, primary_key: true + add :name, :string, null: false + add :payload, :map, default: %{} + add :tags, :map, default: %{} + add :actor, :string + add :source, :string + + timestamps(updated_at: false, type: :utc_datetime_usec) + end + + create index(:fyi_events, [:name]) + create index(:fyi_events, [:actor]) + create index(:fyi_events, [:inserted_at]) + end end """ - Igniter.add_notice(igniter, """ - Add the following to your router.ex inside the browser pipeline: + Igniter.create_new_file(igniter, "priv/repo/migrations/#{filename}", migration_content) + end + end - #{route_code} - """) + defp maybe_add_routes(igniter, opts) do + if opts[:no_ui] do + igniter + else + route_code = + """ + # FYI Admin UI + scope "/fyi", FYI.Web do + pipe_through [:browser] + + live "/", InboxLive, :index + live "/events/:id", InboxLive, :show + end + """ + + Igniter.add_notice(igniter, """ + Add the following to your router.ex inside the browser pipeline: + + #{route_code} + """) + end end - end - defp maybe_add_feedback_component(igniter, opts) do - if opts[:no_feedback] do - igniter - else - web_module = Igniter.Libs.Phoenix.web_module(igniter) - app_name = Igniter.Project.Application.app_name(igniter) - - # Read the template - template_path = Application.app_dir(:fyi, "priv/templates/feedback_component.ex") - - content = - if File.exists?(template_path) do - template_path - |> File.read!() - |> EEx.eval_string(assigns: [web_module: inspect(web_module)]) - else - # Fallback template if priv file not found (during development) - generate_feedback_component(web_module) - end + defp maybe_add_feedback_component(igniter, opts) do + if opts[:no_feedback] do + igniter + else + web_module = Igniter.Libs.Phoenix.web_module(igniter) + app_name = Igniter.Project.Application.app_name(igniter) + + # Read the template + template_path = Application.app_dir(:fyi, "priv/templates/feedback_component.ex") + + content = + if File.exists?(template_path) do + template_path + |> File.read!() + |> EEx.eval_string(assigns: [web_module: inspect(web_module)]) + else + # Fallback template if priv file not found (during development) + generate_feedback_component(web_module) + end - # Determine the target path - app_name_str = to_string(app_name) - target_path = "lib/#{app_name_str}_web/components/fyi/feedback_component.ex" + # Determine the target path + app_name_str = to_string(app_name) + target_path = "lib/#{app_name_str}_web/components/fyi/feedback_component.ex" - igniter - |> Igniter.create_new_file(target_path, content) - |> Igniter.add_notice(""" - Feedback component installed at: #{target_path} + igniter + |> Igniter.create_new_file(target_path, content) + |> Igniter.add_notice(""" + Feedback component installed at: #{target_path} - Usage in any LiveView: + Usage in any LiveView: - import #{inspect(web_module)}.FYI.FeedbackComponent - <.feedback_button /> + import #{inspect(web_module)}.FYI.FeedbackComponent + <.feedback_button /> - Or with customization: + Or with customization: - <.feedback_button - title="Report an Issue" - button_label="Report" - button_icon="🐛" - categories={[{"bug", "Bug"}, {"ux", "UX Issue"}]} - /> - """) + <.feedback_button + title="Report an Issue" + button_label="Report" + button_icon="🐛" + categories={[{"bug", "Bug"}, {"ux", "UX Issue"}]} + /> + """) + end end - end - defp generate_feedback_component(web_module) do - """ - defmodule #{inspect(web_module)}.FYI.FeedbackComponent do - @moduledoc \"\"\" - Feedback component for collecting user feedback. - Customize the styling, categories, and behavior as needed. - \"\"\" - - use Phoenix.LiveComponent - - @default_categories [ - {"bug", "Bug Report"}, - {"feature", "Feature Request"}, - {"improvement", "Improvement"}, - {"other", "Other"} - ] - - @impl true - def mount(socket) do - {:ok, - socket - |> assign(:show_modal, false) - |> assign(:submitted, false)} - end + defp generate_feedback_component(web_module) do + """ + defmodule #{inspect(web_module)}.FYI.FeedbackComponent do + @moduledoc \"\"\" + Feedback component for collecting user feedback. + Customize the styling, categories, and behavior as needed. + \"\"\" - @impl true - def update(assigns, socket) do - socket = - socket - |> assign(assigns) - |> assign_new(:button_label, fn -> "Feedback" end) - |> assign_new(:button_icon, fn -> "💬" end) - |> assign_new(:title, fn -> "Send Feedback" end) - |> assign_new(:categories, fn -> @default_categories end) - - {:ok, socket} - end + use Phoenix.LiveComponent + + @default_categories [ + {"bug", "Bug Report"}, + {"feature", "Feature Request"}, + {"improvement", "Improvement"}, + {"other", "Other"} + ] + + @impl true + def mount(socket) do + {:ok, + socket + |> assign(:show_modal, false) + |> assign(:submitted, false)} + end - @impl true - def handle_event("open", _, socket) do - {:noreply, assign(socket, :show_modal, true)} - end + @impl true + def update(assigns, socket) do + socket = + socket + |> assign(assigns) + |> assign_new(:button_label, fn -> "Feedback" end) + |> assign_new(:button_icon, fn -> "💬" end) + |> assign_new(:title, fn -> "Send Feedback" end) + |> assign_new(:categories, fn -> @default_categories end) + + {:ok, socket} + end - @impl true - def handle_event("close", _, socket) do - {:noreply, - socket - |> assign(:show_modal, false) - |> assign(:submitted, false)} - end + @impl true + def handle_event("open", _, socket) do + {:noreply, assign(socket, :show_modal, true)} + end - @impl true - def handle_event("submit", %{"message" => message, "category" => category, "email" => email}, socket) do - payload = %{message: message, category: category, email: email} - actor = socket.assigns[:current_user_id] || socket.assigns[:user_id] - FYI.emit("feedback.submitted", payload, actor: actor, tags: %{category: category}) - {:noreply, assign(socket, :submitted, true)} - end + @impl true + def handle_event("close", _, socket) do + {:noreply, + socket + |> assign(:show_modal, false) + |> assign(:submitted, false)} + end - @impl true - def render(assigns) do - ~H\"\"\" -