Packages
phoenix_kit
1.7.93
1.7.210
1.7.209
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/phoenix_kit.gen.admin.page.ex
defmodule Mix.Tasks.PhoenixKit.Gen.Admin.Page do
@moduledoc """
Igniter task that generates admin page with automatic route registration.
## Usage
mix phoenix_kit.gen.admin.page "Reports Dashboard"
## Arguments
- `title` - The display title for the page (e.g., "Reports Dashboard")
## Options
- `--url` - The URL path for the page (optional, derived from title if not provided)
- `--category` - The category name (optional, defaults to "General")
- `--icon` - Heroicon name for the tab (optional, defaults to "hero-document-text")
- `--permission` - Permission key for parent tab (optional, defaults to "dashboard")
- `--category-icon` - Heroicon name for the category (optional, defaults to "hero-folder")
## Parent/Child Tab Behavior
- First page in a category creates both parent and child tabs
- Subsequent pages in the same category only add the child tab
- Parent tab path points to the first child's URL
- Routes are automatically generated via the `live_view` field
## Examples
# Simple - uses all defaults
mix phoenix_kit.gen.admin.page "Reports Dashboard"
# With custom category
mix phoenix_kit.gen.admin.page "User Management" --category="Users"
# With custom icon
mix phoenix_kit.gen.admin.page "Analytics" --icon="hero-chart-bar"
# Full control
mix phoenix_kit.gen.admin.page "Reports" --url="/admin/analytics/reports" --category="Analytics" --icon="hero-chart-bar"
"""
@shortdoc "Generates admin page with automatic route registration"
use Igniter.Mix.Task
alias Igniter.Code.Common
alias Igniter.Project.Config
alias PhoenixKit.Install.IgniterHelpers
alias Sourceror.Zipper
@impl Igniter.Mix.Task
def info(_argv, _composing_task) do
%Igniter.Mix.Task.Info{
group: :phoenix_kit,
example: "mix phoenix_kit.gen.admin.page \"Reports Dashboard\"",
schema: [
url: :string,
category: :string,
icon: :string,
permission: :string,
category_icon: :string
],
aliases: [u: :url, c: :category, i: :icon, p: :permission, ci: :category_icon]
}
end
@impl Igniter.Mix.Task
def igniter(igniter) do
opts = igniter.args.options
argv = igniter.args.argv
case parse_args(argv, opts) do
{:ok, {title, category, url}} ->
igniter
|> generate_admin_page(title, category, url, opts)
{:error, message} ->
Igniter.add_notice(igniter, """
❌ Error: #{message}
Usage: mix phoenix_kit.gen.admin.page <title>
Example: mix phoenix_kit.gen.admin.page "Reports Dashboard"
""")
end
end
@impl Mix.Task
def run(argv) do
if "--help" in argv or "-h" in argv do
Mix.shell().info("""
Generates admin page with automatic route registration.
Usage:
mix phoenix_kit.gen.admin.page "Reports Dashboard"
Arguments:
title - The display title for the page
Options:
--url - The URL path (optional, derived from title)
--category - The category name (optional, defaults to "General")
--icon - Heroicon name (optional, defaults to "hero-document-text")
--permission - Permission key (optional, defaults to "dashboard")
--category-icon - Heroicon name for category (optional, defaults to "hero-folder")
Examples:
# Simple
mix phoenix_kit.gen.admin.page "Reports Dashboard"
# With category
mix phoenix_kit.gen.admin.page "Users" --category="People"
# With URL
mix phoenix_kit.gen.admin.page "Reports" --url="/admin/analytics/reports"
Notes:
- Routes are auto-generated via live_view config
- First page in category creates parent tab
- Run: mix compile --force && restart server
""")
:ok
else
super(argv)
end
end
defp parse_args(argv, opts) do
positional_args = Enum.reject(argv, &String.starts_with?(&1, "--"))
case positional_args do
[title] ->
url = Keyword.get(opts, :url, slugify(title))
category = Keyword.get(opts, :category, "General")
{:ok, {title, category, url}}
[] ->
{:error, "not enough arguments. Expected: <title>"}
_ ->
{:error, "invalid arguments"}
end
end
defp generate_admin_page(igniter, title, category, url, opts) do
icon = Keyword.get(opts, :icon, "hero-document-text")
cond do
!String.starts_with?(url, "/") ->
Igniter.add_issue(igniter, {:fatal, "URL must start with '/'", []})
byte_size(title) > 100 ->
Igniter.add_issue(igniter, {:fatal, "Title must be less than 100 characters", []})
true ->
igniter
|> create_page_live_view(title, category, url)
|> add_admin_tabs(title, category, url, icon, opts)
|> print_success_message(title, category, url)
end
end
defp create_page_live_view(igniter, title, category, url) do
category_module_name = Macro.camelize(String.replace(category, " ", "_"))
page_name = derive_page_name(title)
app_name = IgniterHelpers.get_parent_app_name(igniter)
web_module = IgniterHelpers.get_parent_app_module_web(igniter)
web_module_string =
web_module
|> to_string()
|> String.replace_prefix("Elixir.", "")
template_path =
case :code.priv_dir(:phoenix_kit) do
priv_dir when is_list(priv_dir) or is_binary(priv_dir) ->
Path.join(priv_dir, "templates/admin_category_page.ex")
_ ->
"priv/templates/admin_category_page.ex"
end
case File.read(template_path) do
{:ok, template_content} ->
rendered_content =
template_content
|> String.replace("<%= @web_module_prefix %>", web_module_string)
|> String.replace("<%= @page_name %>", to_string(page_name))
|> String.replace("<%= @page_title %>", to_string(title))
|> String.replace("<%= @url %>", to_string(url))
|> String.replace("<%= @category %>", category_module_name)
file_path = build_live_view_file_path(app_name, category, page_name)
Igniter.create_new_file(igniter, file_path, rendered_content, on_format: :skip)
{:error, reason} ->
igniter
|> Igniter.add_issue({:fatal, "Failed to read template file: #{reason}", []})
end
end
defp add_admin_tabs(igniter, title, category, url, icon, opts) do
web_module = IgniterHelpers.get_parent_app_module_web(igniter)
category_icon = Keyword.get(opts, :category_icon, "hero-folder")
permission = Keyword.get(opts, :permission, "dashboard")
parent_id = derive_parent_tab_id(category)
child_id = derive_child_tab_id(category, derive_page_name(title))
child_live_view_module = build_live_view_module(web_module, category, derive_page_name(title))
child_tab = %{
id: child_id,
label: title,
icon: icon,
path: url,
parent: parent_id,
permission: permission,
priority: calculate_child_priority(category, derive_page_name(title)),
live_view: {child_live_view_module, :index}
}
# Create parent tab (points to first child's URL)
parent_tab = %{
id: parent_id,
label: category,
icon: category_icon,
path: url,
permission: permission,
priority: calculate_parent_priority(category),
group: :admin_modules,
subtab_display: :when_active,
highlight_with_subtabs: false
}
# Use IgniterConfig to modify the config
Config.configure(
igniter,
"config.exs",
:phoenix_kit,
[:admin_dashboard_tabs],
[parent_tab, child_tab],
updater: fn zipper ->
case extract_current_value(zipper) do
{:ok, existing_tabs} when is_list(existing_tabs) ->
parent_exists? = Enum.any?(existing_tabs, fn t -> t[:id] == parent_id end)
child_exists? = Enum.any?(existing_tabs, fn t -> t[:id] == child_id end)
if child_exists? do
{:error,
"A page with ID #{inspect(child_id)} already exists in category '#{category}'"}
else
siblings =
Enum.filter(existing_tabs, fn t ->
t[:parent] == parent_id or (t[:id] == parent_id and parent_exists?)
end)
url_duplicate? =
Enum.any?(siblings, fn t ->
t[:path] == url and t[:id] != child_id
end)
if url_duplicate? do
{:error, "A page with URL '#{url}' already exists in category '#{category}'"}
else
updated_tabs =
if parent_exists? do
existing_tabs ++ [child_tab]
else
existing_tabs ++ [parent_tab, child_tab]
end
{:ok, Common.replace_code(zipper, updated_tabs)}
end
end
_ ->
{:ok, Common.replace_code(zipper, [parent_tab, child_tab])}
end
end
)
end
defp derive_parent_tab_id(category) do
category
|> String.downcase()
|> String.replace(" ", "_")
|> then(&:"admin_#{&1}")
end
defp derive_child_tab_id(category, page_name) do
category_slug = category |> String.downcase() |> String.replace(" ", "_")
page_slug = page_name |> String.downcase() |> String.replace(" ", "_")
:"admin_#{category_slug}_#{page_slug}"
end
defp derive_page_name(title) do
title
|> String.downcase()
|> String.replace(~r/[^a-z0-9\s]/, "")
|> String.replace(~r/\s+/, "_")
|> Macro.camelize()
end
defp build_live_view_module(web_module, category, page_name) do
category_module_name = Macro.camelize(String.replace(category, " ", "_"))
Module.concat([web_module, PhoenixKit, Admin, category_module_name, page_name])
end
defp calculate_parent_priority(category) do
category
|> String.downcase()
|> :erlang.phash2()
|> rem(90)
|> Kernel.+(700)
end
defp calculate_child_priority(category, page_name) do
parent_prio = calculate_parent_priority(category)
offset =
page_name
|> :erlang.phash2()
|> rem(9)
|> Kernel.+(1)
parent_prio + offset
end
defp slugify(title) do
title
|> String.downcase()
|> String.replace(~r/[^a-z0-9\s-]/, "")
|> String.replace(~r/\s+/, "-")
|> then(&"/admin/#{&1}")
end
# Extracts the current value from a zipper
defp extract_current_value(zipper) do
current_node = Zipper.node(zipper)
case Code.eval_quoted(current_node) do
{value, _binding} -> {:ok, value}
end
rescue
_ -> :error
end
defp print_success_message(igniter, title, category, url) do
web_module = IgniterHelpers.get_parent_app_module_web(igniter)
web_module_string =
web_module
|> to_string()
|> String.replace_prefix("Elixir.", "")
category_module_name = Macro.camelize(String.replace(category, " ", "_"))
page_name = derive_page_name(title)
page_module =
"#{web_module_string}.PhoenixKit.Admin.#{category_module_name}.#{page_name}"
Igniter.add_notice(igniter, """
✅ Admin page generated!
Page: #{page_module}
Category: #{category}
URL: #{url}
📝 Routes are auto-generated via live_view config
Then: mix compile --force && restart server
""")
end
defp build_live_view_file_path(app_name, category, page_name) do
web_path =
app_name
|> to_string()
|> Kernel.<>("_web")
|> String.downcase()
category_path = category |> String.replace(" ", "_") |> String.downcase()
file_name = String.downcase(page_name)
"lib/#{web_path}/phoenix_kit/admin/#{category_path}/#{file_name}.ex"
end
end