Packages
phoenix_kit
1.7.39
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/phoenix_kit_web/integration.ex
# credo:disable-for-this-file Credo.Check.Refactor.LongQuoteBlocks
defmodule PhoenixKitWeb.Integration do
@moduledoc """
Integration helpers for adding PhoenixKit to Phoenix applications.
## Basic Usage
Add to your router:
defmodule MyAppWeb.Router do
use MyAppWeb, :router
import PhoenixKitWeb.Integration
# Add PhoenixKit routes
phoenix_kit_routes() # Default: /phoenix_kit prefix
end
## Automatic Integration
When you run `mix phoenix_kit.install`, the following is automatically added to your
`:browser` pipeline:
plug PhoenixKitWeb.Plugs.Integration
This plug handles all PhoenixKit features including maintenance mode, and ensures
they work across your entire application
## Layout Integration
Configure parent layouts in config.exs:
config :phoenix_kit,
repo: MyApp.Repo,
layout: {MyAppWeb.Layouts, :app},
root_layout: {MyAppWeb.Layouts, :root}
## Authentication Callbacks
Use in your app's live_sessions:
- `:phoenix_kit_mount_current_scope` - Mounts user and scope (recommended)
- `:phoenix_kit_ensure_authenticated_scope` - Requires authentication
- `:phoenix_kit_redirect_if_authenticated_scope` - Redirects if logged in
## Routes Created
Authentication routes:
- /users/register, /users/log-in, /users/magic-link
- /users/reset-password, /users/confirm
- /users/log-out (GET/DELETE)
User dashboard routes (if enabled, default: true):
- /dashboard, /dashboard/settings
- /dashboard/settings/confirm-email/:token
Admin routes (Owner/Admin only):
- /admin, /admin/users, /admin/users/roles
- /admin/users/live_sessions, /admin/users/sessions
- /admin/settings, /admin/modules
Public pages routes (if Pages module enabled):
- {prefix}/pages/* (explicit prefix - e.g., /phoenix_kit/pages/test)
- /* (catch-all at root level - e.g., /test, /blog/post)
- Both routes serve published pages from priv/static/pages/*.md
- The catch-all can optionally serve a custom 404 markdown file when enabled
- Example: /test or /phoenix_kit/pages/test renders test.md
## Configuration
You can disable the user dashboard by setting the environment variable in your config:
# config/dev.exs or config/runtime.exs
config :phoenix_kit, user_dashboard_enabled: false
This will disable all dashboard routes (/dashboard/*). Users trying to access
the dashboard will get a 404 error.
## DaisyUI Setup
1. Install: `npm install daisyui@latest`
2. Add to tailwind.config.js:
- Content: `"../../deps/phoenix_kit"`
- Plugin: `require('daisyui')`
## Layout Templates
Use `{@inner_content}` not `render_slot(@inner_block)`:
<%!-- Correct --%>
<main>{@inner_content}</main>
## Scope Usage in Templates
<%= if PhoenixKit.Users.Auth.Scope.authenticated?(@phoenix_kit_current_scope) do %>
Welcome, {PhoenixKit.Users.Auth.Scope.user_email(@phoenix_kit_current_scope)}!
<% end %>
"""
alias PhoenixKitWeb
alias PhoenixKitWeb.Routes.BlogRoutes
alias PhoenixKitWeb.Routes.EmailsRoutes
alias PhoenixKitWeb.Routes.PublishingRoutes
alias PhoenixKitWeb.Routes.ReferralsRoutes
alias PhoenixKitWeb.Routes.ShopRoutes
alias PhoenixKitWeb.Routes.TicketsRoutes
@doc """
Creates locale-aware routing scopes based on enabled languages.
This macro generates both a localized scope (e.g., `/en/`) and a non-localized
scope for backward compatibility. The locale pattern is dynamically generated
from the database-stored enabled language codes.
## Examples
locale_scope do
live "/admin", DashboardLive, :index
end
# Generates routes like:
# /phoenix_kit/en/admin (with locale)
# /phoenix_kit/admin (without locale, defaults to "en")
"""
defmacro locale_scope(opts \\ [], do: block) do
# Get URL prefix at compile time
raw_prefix =
try do
PhoenixKit.Config.get_url_prefix()
rescue
_ -> "/phoenix_kit"
end
url_prefix =
case raw_prefix do
"" -> "/"
prefix -> prefix
end
quote do
alias PhoenixKit.Modules.Languages
# Define locale validation pipeline
pipeline :phoenix_kit_locale_validation do
plug PhoenixKitWeb.Users.Auth, :phoenix_kit_validate_and_set_locale
end
# Localized scope with flexible locale pattern
# Accepts both base codes (en, es) and full dialect codes (en-US, es-MX)
# Full dialect codes are automatically redirected to base codes by the validation plug
# This ensures backward compatibility with old URLs while enforcing base code standard
scope "#{unquote(url_prefix)}/:locale",
PhoenixKitWeb,
Keyword.put(unquote(opts), :locale, ~r/^[a-z]{2}(?:-[A-Za-z0-9]{2,})?$/) do
pipe_through [:browser, :phoenix_kit_auto_setup, :phoenix_kit_locale_validation]
unquote(block)
end
# Non-localized scope for backward compatibility (defaults to "en")
scope unquote(url_prefix), PhoenixKitWeb, unquote(opts) do
pipe_through [:browser, :phoenix_kit_auto_setup, :phoenix_kit_locale_validation]
unquote(block)
end
end
end
# Helper function to generate pipeline definitions
defp generate_pipelines do
quote do
alias PhoenixKit.Modules.Languages
# Define the auto-setup pipeline
pipeline :phoenix_kit_auto_setup do
plug PhoenixKitWeb.Plugs.RequestTimer
plug PhoenixKitWeb.Users.Auth, :fetch_phoenix_kit_current_user
plug PhoenixKitWeb.Integration, :phoenix_kit_auto_setup
end
pipeline :phoenix_kit_redirect_if_authenticated do
plug PhoenixKitWeb.Users.Auth, :phoenix_kit_redirect_if_user_is_authenticated
end
pipeline :phoenix_kit_require_authenticated do
plug PhoenixKitWeb.Users.Auth, :fetch_phoenix_kit_current_user
plug PhoenixKitWeb.Users.Auth, :phoenix_kit_require_authenticated_user
end
pipeline :phoenix_kit_admin_only do
plug PhoenixKitWeb.Users.Auth, :fetch_phoenix_kit_current_user
plug PhoenixKitWeb.Users.Auth, :fetch_phoenix_kit_current_scope
plug PhoenixKitWeb.Users.Auth, :phoenix_kit_require_admin
end
# Define API pipeline for JSON endpoints
pipeline :phoenix_kit_api do
plug :accepts, ["json"]
end
# Define locale validation pipeline
pipeline :phoenix_kit_locale_validation do
plug PhoenixKitWeb.Users.Auth, :phoenix_kit_validate_and_set_locale
end
# Define shop session pipeline (ensures persistent cart session)
pipeline :phoenix_kit_shop_session do
plug PhoenixKit.Modules.Shop.Web.Plugs.ShopSession
end
end
end
# Helper function to generate basic scope routes
defp generate_basic_scope(url_prefix) do
quote do
scope unquote(url_prefix), PhoenixKitWeb do
pipe_through [:browser, :phoenix_kit_auto_setup]
post "/users/log-in", Users.Session, :create
delete "/users/log-out", Users.Session, :delete
get "/users/log-out", Users.Session, :get_logout
get "/users/magic-link/:token", Users.MagicLinkVerify, :verify
# Dashboard context switching (multi-selector with key, must come before legacy route)
post "/context/:key/:id", ContextController, :set
# Dashboard context switching (legacy single selector)
post "/context/:id", ContextController, :set
# OAuth routes for external provider authentication
get "/users/auth/:provider", Users.OAuth, :request
get "/users/auth/:provider/callback", Users.OAuth, :callback
# Magic Link Registration routes
get "/users/register/verify/:token", Users.MagicLinkRegistrationVerify, :verify
# Note: Email webhook moved to generate_emails_routes/1 (separate scope)
# Storage API routes (file upload and serving)
post "/api/upload", UploadController, :create
get "/file/:file_id/:variant/:token", FileController, :show
get "/api/files/:file_id/info", FileController, :info
# Cookie consent widget config (public API for JS auto-injection)
get "/api/consent-config", Controllers.ConsentConfigController, :config
# Pages routes temporarily disabled
# get "/pages/*path", PagesController, :show
end
# Sync API routes (JSON API - accepts JSON from remote PhoenixKit sites)
scope unquote(url_prefix) do
pipe_through [:phoenix_kit_api]
post "/sync/api/register-connection",
PhoenixKit.Modules.Sync.Web.ApiController,
:register_connection
post "/sync/api/delete-connection",
PhoenixKit.Modules.Sync.Web.ApiController,
:delete_connection
post "/sync/api/verify-connection",
PhoenixKit.Modules.Sync.Web.ApiController,
:verify_connection
post "/sync/api/update-status",
PhoenixKit.Modules.Sync.Web.ApiController,
:update_status
post "/sync/api/get-connection-status",
PhoenixKit.Modules.Sync.Web.ApiController,
:get_connection_status
post "/sync/api/list-tables",
PhoenixKit.Modules.Sync.Web.ApiController,
:list_tables
post "/sync/api/pull-data",
PhoenixKit.Modules.Sync.Web.ApiController,
:pull_data
post "/sync/api/table-schema",
PhoenixKit.Modules.Sync.Web.ApiController,
:table_schema
post "/sync/api/table-records",
PhoenixKit.Modules.Sync.Web.ApiController,
:table_records
get "/sync/api/status", PhoenixKit.Modules.Sync.Web.ApiController, :status
end
# Sync WebSocket - forward to plug for websocket upgrade handling
# Uses url_prefix to be consistent with API routes
forward "#{unquote(url_prefix)}/sync/websocket", PhoenixKit.Modules.Sync.Web.SocketPlug
# Note: Email export routes moved to generate_emails_routes/1 (separate scope)
# PhoenixKit static assets (no CSRF protection needed for static files)
scope unquote(url_prefix), PhoenixKitWeb do
pipe_through [:phoenix_kit_api]
get "/assets/:file", AssetsController, :serve
end
# Sitemap routes - public XML/XSL endpoints, no session/CSRF/auto_setup needed
scope unquote(url_prefix) do
get "/sitemap.xml", PhoenixKit.Modules.Sitemap.Web.Controller, :xml
get "/sitemap.html", PhoenixKit.Modules.Sitemap.Web.Controller, :html
get "/sitemaps/:filename", PhoenixKit.Modules.Sitemap.Web.Controller, :module_sitemap
get "/sitemap.xsl", PhoenixKit.Modules.Sitemap.Web.Controller, :xsl_stylesheet
get "/assets/sitemap/:style", PhoenixKit.Modules.Sitemap.Web.Controller, :xsl_stylesheet
get "/assets/sitemap-index/:style",
PhoenixKit.Modules.Sitemap.Web.Controller,
:xsl_index_stylesheet
end
# Billing webhook routes - uses PhoenixKit.Modules.Billing namespace (no PhoenixKitWeb prefix)
scope unquote(url_prefix) do
pipe_through [:phoenix_kit_api]
post "/webhooks/billing/stripe",
PhoenixKit.Modules.Billing.Web.WebhookController,
:stripe
post "/webhooks/billing/paypal",
PhoenixKit.Modules.Billing.Web.WebhookController,
:paypal
post "/webhooks/billing/razorpay",
PhoenixKit.Modules.Billing.Web.WebhookController,
:razorpay
end
# Shop public routes are generated via generate_shop_public_routes/1 helper
# This supports locale-prefixed URLs (/:locale/shop/...) with language switching
# Shop user dashboard routes (requires authentication)
scope unquote(url_prefix) do
pipe_through [:browser, :phoenix_kit_auto_setup, :phoenix_kit_require_authenticated]
live_session :phoenix_kit_shop_user,
on_mount: [{PhoenixKitWeb.Users.Auth, :phoenix_kit_ensure_authenticated_scope}] do
live "/dashboard/orders", PhoenixKit.Modules.Shop.Web.UserOrders, :index,
as: :shop_user_orders
live "/dashboard/orders/:uuid", PhoenixKit.Modules.Shop.Web.UserOrderDetails, :show,
as: :shop_user_order_details
live "/dashboard/billing-profiles",
PhoenixKit.Modules.Billing.Web.UserBillingProfiles,
:index,
as: :user_billing_profiles
live "/dashboard/billing-profiles/new",
PhoenixKit.Modules.Billing.Web.UserBillingProfileForm,
:new,
as: :user_billing_profile_new
live "/dashboard/billing-profiles/:id/edit",
PhoenixKit.Modules.Billing.Web.UserBillingProfileForm,
:edit,
as: :user_billing_profile_edit
end
end
end
end
# Helper function to generate catch-all root route for pages
# This allows accessing pages from the root level (e.g., /test, /blog/post)
# Must be placed at the end of the router to not interfere with other routes
defp generate_pages_catch_all do
quote do
# Catch-all route for published pages at root level
# This route should be last to avoid conflicting with app routes
# scope "/", PhoenixKitWeb do
# pipe_through [:browser, :phoenix_kit_auto_setup]
#
# # Catch-all for root-level pages (must be last route)
# get "/*path", PagesController, :show
# end
end
end
# ============================================================================
# Shared Route Definitions
# ============================================================================
# These macros generate route definitions that are shared between localized
# and non-localized scopes. This eliminates code duplication and reduces
# compile time by ~50% for router files.
# ============================================================================
# Generates authentication routes (register, login, magic-link, reset-password)
defmacro phoenix_kit_auth_routes(suffix) do
session_name = :"phoenix_kit_redirect_if_user_is_authenticated#{suffix}"
quote do
live_session unquote(session_name),
on_mount: [{PhoenixKitWeb.Users.Auth, :phoenix_kit_redirect_if_authenticated_scope}] do
live "/users/register", Users.Registration, :new, as: :user_registration
live "/users/register/magic-link", Users.MagicLinkRegistrationRequest, :new,
as: :user_magic_link_registration_request
live "/users/register/complete/:token", Users.MagicLinkRegistration, :complete,
as: :user_magic_link_registration
live "/users/log-in", Users.Login, :new, as: :user_login
live "/users/magic-link", Users.MagicLink, :new, as: :user_magic_link
live "/users/reset-password", Users.ForgotPassword, :new, as: :user_reset_password
live "/users/reset-password/:token", Users.ResetPassword, :edit,
as: :user_reset_password_edit
end
end
end
# Generates user confirmation routes
defmacro phoenix_kit_confirmation_routes(suffix) do
session_name = :"phoenix_kit_current_user#{suffix}"
quote do
live_session unquote(session_name),
on_mount: [{PhoenixKitWeb.Users.Auth, :phoenix_kit_mount_current_scope}] do
live "/users/confirm/:token", Users.Confirmation, :edit, as: :user_confirmation
live "/users/confirm", Users.ConfirmationInstructions, :new,
as: :user_confirmation_instructions
end
end
end
# Generates all admin routes
defmacro phoenix_kit_admin_routes(suffix) do
session_name = :"phoenix_kit_admin#{suffix}"
# Auto-generate routes for custom admin tabs that specify live_view
# Skip when compiling PhoenixKit's own dev/test router — parent modules don't exist
custom_admin_routes = compile_custom_admin_routes(__CALLER__.module)
# Get external route module AST outside quote to avoid require/alias inside quote
emails_admin = EmailsRoutes.admin_routes()
{tickets_admin, publishing_admin, referrals_admin} =
if suffix == :_locale do
{
TicketsRoutes.admin_locale_routes(),
PublishingRoutes.admin_locale_routes(),
ReferralsRoutes.admin_locale_routes()
}
else
{
TicketsRoutes.admin_routes(),
PublishingRoutes.admin_routes(),
ReferralsRoutes.admin_routes()
}
end
quote do
live_session unquote(session_name),
on_mount: [{PhoenixKitWeb.Users.Auth, :phoenix_kit_ensure_admin}] do
# Core admin routes (under PhoenixKitWeb alias from parent scope)
live "/admin", Live.Dashboard, :index
live "/admin/users", Live.Users.Users, :index
live "/admin/users/new", Users.UserForm, :new, as: :user_form
live "/admin/users/edit/:id", Users.UserForm, :edit, as: :user_form_edit
live "/admin/users/view/:id", Live.Users.UserDetails, :show
live "/admin/users/roles", Live.Users.Roles, :index
live "/admin/users/permissions", Live.Users.PermissionsMatrix, :index
live "/admin/users/live_sessions", Live.Users.LiveSessions, :index
live "/admin/users/sessions", Live.Users.Sessions, :index
live "/admin/media", Live.Users.Media, :index
live "/admin/media/:file_id", Live.Users.MediaDetail, :show
live "/admin/media/selector", Live.Users.MediaSelector, :index
live "/admin/settings", Live.Settings, :index
live "/admin/settings/users", Live.Settings.Users, :index
live "/admin/settings/organization", Live.Settings.Organization, :index
live "/admin/modules", Live.Modules, :index
# Posts module routes
live "/admin/posts", Live.Modules.Posts.Posts, :index
live "/admin/posts/new", Live.Modules.Posts.Edit, :new
live "/admin/posts/groups", Live.Modules.Posts.Groups, :index
live "/admin/posts/groups/new", Live.Modules.Posts.GroupEdit, :new
live "/admin/posts/groups/:id/edit", Live.Modules.Posts.GroupEdit, :edit
live "/admin/posts/:id", Live.Modules.Posts.Details, :show
live "/admin/posts/:id/edit", Live.Modules.Posts.Edit, :edit
live "/admin/settings/posts", Live.Modules.Posts.Settings, :index
live "/admin/settings/languages", Live.Modules.Languages, :index
live "/admin/settings/languages/frontend", Live.Modules.Languages, :frontend
live "/admin/settings/languages/backend", Live.Modules.Languages, :backend
live "/admin/settings/legal", Live.Modules.Legal.Settings, :index
live "/admin/settings/maintenance", Live.Modules.Maintenance.Settings, :index
live "/admin/settings/seo", Live.Settings.SEO, :index
live "/admin/settings/media", Live.Modules.Storage.Settings, :index
live "/admin/settings/media/buckets/new", Live.Modules.Storage.BucketForm, :new
live "/admin/settings/media/buckets/:id/edit", Live.Modules.Storage.BucketForm, :edit
live "/admin/settings/media/dimensions", Live.Modules.Storage.Dimensions, :index
live "/admin/settings/media/dimensions/new/image",
Live.Modules.Storage.DimensionForm,
:new_image
live "/admin/settings/media/dimensions/new/video",
Live.Modules.Storage.DimensionForm,
:new_video
live "/admin/settings/media/dimensions/:id/edit",
Live.Modules.Storage.DimensionForm,
:edit
# Jobs
live "/admin/jobs", Live.Modules.Jobs.Index, :index
# Module admin routes (use alias: false to prevent PhoenixKitWeb prefix
# since these modules use their own namespaces like PhoenixKit.Modules.*)
scope "/", alias: false do
# Sitemap settings
live "/admin/settings/sitemap",
PhoenixKit.Modules.Sitemap.Web.Settings,
:index,
as: :sitemap_settings
# Billing admin routes
live "/admin/billing", PhoenixKit.Modules.Billing.Web.Index, :index, as: :billing_index
live "/admin/billing/orders", PhoenixKit.Modules.Billing.Web.Orders, :index,
as: :billing_orders
live "/admin/billing/orders/new", PhoenixKit.Modules.Billing.Web.OrderForm, :new,
as: :billing_order_new
live "/admin/billing/orders/:id", PhoenixKit.Modules.Billing.Web.OrderDetail, :show,
as: :billing_order_detail
live "/admin/billing/orders/:id/edit", PhoenixKit.Modules.Billing.Web.OrderForm, :edit,
as: :billing_order_edit
live "/admin/billing/invoices", PhoenixKit.Modules.Billing.Web.Invoices, :index,
as: :billing_invoices
live "/admin/billing/invoices/:id", PhoenixKit.Modules.Billing.Web.InvoiceDetail, :show,
as: :billing_invoice_detail
live "/admin/billing/invoices/:id/print",
PhoenixKit.Modules.Billing.Web.InvoicePrint,
:print,
as: :billing_invoice_print
live "/admin/billing/invoices/:id/receipt",
PhoenixKit.Modules.Billing.Web.ReceiptPrint,
:receipt,
as: :billing_receipt_print
live "/admin/billing/invoices/:id/credit-note/:transaction_id",
PhoenixKit.Modules.Billing.Web.CreditNotePrint,
:credit_note,
as: :billing_credit_note
live "/admin/billing/invoices/:id/payment/:transaction_id",
PhoenixKit.Modules.Billing.Web.PaymentConfirmationPrint,
:payment_confirmation,
as: :billing_payment_confirmation
live "/admin/billing/transactions", PhoenixKit.Modules.Billing.Web.Transactions, :index,
as: :billing_transactions
live "/admin/billing/subscriptions",
PhoenixKit.Modules.Billing.Web.Subscriptions,
:index,
as: :billing_subscriptions
live "/admin/billing/subscriptions/new",
PhoenixKit.Modules.Billing.Web.SubscriptionForm,
:new,
as: :billing_subscription_new
live "/admin/billing/subscriptions/:id",
PhoenixKit.Modules.Billing.Web.SubscriptionDetail,
:show,
as: :billing_subscription_detail
live "/admin/billing/plans", PhoenixKit.Modules.Billing.Web.SubscriptionPlans, :index,
as: :billing_plans
live "/admin/billing/plans/new",
PhoenixKit.Modules.Billing.Web.SubscriptionPlanForm,
:new,
as: :billing_plan_new
live "/admin/billing/plans/:id/edit",
PhoenixKit.Modules.Billing.Web.SubscriptionPlanForm,
:edit,
as: :billing_plan_edit
live "/admin/billing/profiles", PhoenixKit.Modules.Billing.Web.BillingProfiles, :index,
as: :billing_profiles
live "/admin/billing/profiles/new",
PhoenixKit.Modules.Billing.Web.BillingProfileForm,
:new,
as: :billing_profile_new
live "/admin/billing/profiles/:id/edit",
PhoenixKit.Modules.Billing.Web.BillingProfileForm,
:edit,
as: :billing_profile_edit
live "/admin/billing/currencies", PhoenixKit.Modules.Billing.Web.Currencies, :index,
as: :billing_currencies
live "/admin/settings/billing", PhoenixKit.Modules.Billing.Web.Settings, :settings,
as: :billing_settings
live "/admin/settings/billing/providers",
PhoenixKit.Modules.Billing.Web.ProviderSettings,
:index,
as: :billing_provider_settings
# DB Explorer routes
live "/admin/db", PhoenixKit.Modules.DB.Web.Index, :index, as: :db_index
live "/admin/db/activity", PhoenixKit.Modules.DB.Web.Activity, :activity,
as: :db_activity
live "/admin/db/:schema/:table", PhoenixKit.Modules.DB.Web.Show, :show, as: :db_show
# Comments module routes
live "/admin/comments", PhoenixKit.Modules.Comments.Web.Index, :index,
as: :comments_index
live "/admin/settings/comments", PhoenixKit.Modules.Comments.Web.Settings, :settings,
as: :comments_settings
# Sync module routes
live "/admin/sync", PhoenixKit.Modules.Sync.Web.Index, :index, as: :sync_index
live "/admin/sync/connections", PhoenixKit.Modules.Sync.Web.ConnectionsLive, :index,
as: :sync_connections
live "/admin/sync/history", PhoenixKit.Modules.Sync.Web.History, :index,
as: :sync_history
# Entities module routes
live "/admin/entities", PhoenixKit.Modules.Entities.Web.Entities, :index, as: :entities
live "/admin/entities/new", PhoenixKit.Modules.Entities.Web.EntityForm, :new,
as: :entities_new
live "/admin/entities/:id/edit", PhoenixKit.Modules.Entities.Web.EntityForm, :edit,
as: :entities_edit
live "/admin/entities/:entity_slug/data",
PhoenixKit.Modules.Entities.Web.DataNavigator,
:entity,
as: :entities_data_entity
live "/admin/entities/:entity_slug/data/new",
PhoenixKit.Modules.Entities.Web.DataForm,
:new,
as: :entities_data_new
live "/admin/entities/:entity_slug/data/:id",
PhoenixKit.Modules.Entities.Web.DataForm,
:show,
as: :entities_data_show
live "/admin/entities/:entity_slug/data/:id/edit",
PhoenixKit.Modules.Entities.Web.DataForm,
:edit,
as: :entities_data_edit
live "/admin/settings/entities",
PhoenixKit.Modules.Entities.Web.EntitiesSettings,
:index,
as: :entities_settings
# Shop admin routes
live "/admin/shop", PhoenixKit.Modules.Shop.Web.Dashboard, :index, as: :shop_dashboard
live "/admin/shop/products", PhoenixKit.Modules.Shop.Web.Products, :index,
as: :shop_products
live "/admin/shop/products/new", PhoenixKit.Modules.Shop.Web.ProductForm, :new,
as: :shop_product_new
live "/admin/shop/products/:id", PhoenixKit.Modules.Shop.Web.ProductDetail, :show,
as: :shop_product_detail
live "/admin/shop/products/:id/edit", PhoenixKit.Modules.Shop.Web.ProductForm, :edit,
as: :shop_product_edit
live "/admin/shop/categories", PhoenixKit.Modules.Shop.Web.Categories, :index,
as: :shop_categories
live "/admin/shop/categories/new", PhoenixKit.Modules.Shop.Web.CategoryForm, :new,
as: :shop_category_new
live "/admin/shop/categories/:id/edit", PhoenixKit.Modules.Shop.Web.CategoryForm, :edit,
as: :shop_category_edit
live "/admin/shop/shipping", PhoenixKit.Modules.Shop.Web.ShippingMethods, :index,
as: :shop_shipping_methods
live "/admin/shop/shipping/new", PhoenixKit.Modules.Shop.Web.ShippingMethodForm, :new,
as: :shop_shipping_new
live "/admin/shop/shipping/:id/edit",
PhoenixKit.Modules.Shop.Web.ShippingMethodForm,
:edit,
as: :shop_shipping_edit
live "/admin/shop/carts", PhoenixKit.Modules.Shop.Web.Carts, :index, as: :shop_carts
live "/admin/shop/settings", PhoenixKit.Modules.Shop.Web.Settings, :index,
as: :shop_settings
live "/admin/shop/settings/options",
PhoenixKit.Modules.Shop.Web.OptionsSettings,
:index,
as: :shop_options_settings
live "/admin/shop/settings/import-configs",
PhoenixKit.Modules.Shop.Web.ImportConfigs,
:index,
as: :shop_import_configs
live "/admin/shop/imports", PhoenixKit.Modules.Shop.Web.Imports, :index,
as: :shop_imports
live "/admin/shop/imports/:uuid", PhoenixKit.Modules.Shop.Web.ImportShow, :show,
as: :shop_import_show
live "/admin/shop/test", PhoenixKit.Modules.Shop.Web.TestShop, :index, as: :shop_test
# AI module routes
live "/admin/ai", PhoenixKit.Modules.AI.Web.Endpoints, :index, as: :ai_index
live "/admin/ai/endpoints", PhoenixKit.Modules.AI.Web.Endpoints, :endpoints,
as: :ai_endpoints
live "/admin/ai/usage", PhoenixKit.Modules.AI.Web.Endpoints, :usage, as: :ai_usage
live "/admin/ai/endpoints/new", PhoenixKit.Modules.AI.Web.EndpointForm, :new,
as: :ai_endpoint_new
live "/admin/ai/endpoints/:id/edit", PhoenixKit.Modules.AI.Web.EndpointForm, :edit,
as: :ai_endpoint_edit
live "/admin/ai/prompts", PhoenixKit.Modules.AI.Web.Prompts, :index, as: :ai_prompts
live "/admin/ai/prompts/new", PhoenixKit.Modules.AI.Web.PromptForm, :new,
as: :ai_prompt_new
live "/admin/ai/prompts/:id/edit", PhoenixKit.Modules.AI.Web.PromptForm, :edit,
as: :ai_prompt_edit
# Routes from external route modules
unquote(emails_admin)
unquote(tickets_admin)
unquote(publishing_admin)
unquote(referrals_admin)
# Custom admin routes from :admin_dashboard_tabs config
# Tabs with live_view: {Module, :action} get auto-generated routes
# in the shared admin live_session for seamless navigation
unquote_splicing(custom_admin_routes)
end
end
end
end
# Generates user dashboard routes (conditional on config)
defmacro phoenix_kit_dashboard_routes(suffix) do
session_name = :"phoenix_kit_user_dashboard#{suffix}"
quote do
if unquote(PhoenixKit.Config.user_dashboard_enabled?()) do
live_session unquote(session_name),
on_mount: [
{PhoenixKitWeb.Users.Auth, :phoenix_kit_ensure_authenticated_scope},
{PhoenixKitWeb.Dashboard.ContextProvider, :default}
] do
live "/dashboard", Live.Dashboard.Index, :index
live "/dashboard/settings", Live.Dashboard.Settings, :edit
live "/dashboard/settings/confirm-email/:token",
Live.Dashboard.Settings,
:confirm_email
end
end
end
end
# Reads :admin_dashboard_tabs config at compile time and generates
# `live` route declarations for tabs that specify a `live_view` field.
# Returns a list of quoted expressions for use with unquote_splicing.
#
# ## Tab config example
#
# config :phoenix_kit, :admin_dashboard_tabs, [
# %{id: :admin_analytics, label: "Analytics", path: "/admin/analytics",
# live_view: {MyAppWeb.AnalyticsLive, :index}, permission: "dashboard"}
# ]
#
@doc false
def compile_custom_admin_routes(caller_module) do
# PhoenixKit's own router is only for dev/test — parent app modules
# aren't available when it compiles, so skip custom route generation.
if caller_module == PhoenixKitWeb.Router do
[]
else
compile_custom_admin_routes_internal()
end
end
defp compile_custom_admin_routes_internal do
case Application.get_env(:phoenix_kit, :admin_dashboard_tabs) do
tabs when is_list(tabs) ->
tabs
|> Enum.filter(fn tab ->
is_map(tab) and Map.has_key?(tab, :live_view) and
match?({module, _action} when is_atom(module), tab.live_view)
end)
|> Enum.map(fn tab ->
{module, action} = tab.live_view
path = tab[:path] || raise "Tab #{tab[:id]} has :live_view but no :path"
route_opts =
case tab[:id] do
nil -> []
id -> [as: id]
end
quote do
live unquote(path), unquote(module), unquote(action), unquote(route_opts)
end
end)
_ ->
[]
end
end
# ============================================================================
# Route Scope Generators
# ============================================================================
# Helper function to generate localized routes
defp generate_localized_routes(url_prefix, pattern) do
quote do
# Localized scope with locale parameter
scope "#{unquote(url_prefix)}/:locale", PhoenixKitWeb,
locale: ~r/^(#{unquote(pattern)})$/ do
pipe_through [:browser, :phoenix_kit_auto_setup, :phoenix_kit_locale_validation]
# POST routes for authentication (needed for locale-prefixed form submissions)
post "/users/log-in", Users.Session, :create
delete "/users/log-out", Users.Session, :delete
get "/users/log-out", Users.Session, :get_logout
get "/users/magic-link/:token", Users.MagicLinkVerify, :verify
# OAuth routes
get "/users/auth/:provider", Users.OAuth, :request
get "/users/auth/:provider/callback", Users.OAuth, :callback
# Magic Link Registration
get "/users/register/verify/:token", Users.MagicLinkRegistrationVerify, :verify
phoenix_kit_auth_routes(:_locale)
phoenix_kit_confirmation_routes(:_locale)
phoenix_kit_admin_routes(:_locale)
phoenix_kit_dashboard_routes(:_locale)
end
end
end
# Helper function to generate non-localized routes
defp generate_non_localized_routes(url_prefix) do
quote do
# Non-localized scope for backward compatibility (defaults to "en")
scope unquote(url_prefix), PhoenixKitWeb do
pipe_through [:browser, :phoenix_kit_auto_setup, :phoenix_kit_locale_validation]
phoenix_kit_auth_routes(:"")
phoenix_kit_confirmation_routes(:"")
phoenix_kit_admin_routes(:"")
phoenix_kit_dashboard_routes(:"")
end
end
end
defmacro phoenix_kit_routes do
# OAuth configuration is handled by PhoenixKit.Workers.OAuthConfigLoader
# which runs synchronously during supervisor startup
# No need for async spawn() here anymore
# Get URL prefix at compile time and handle empty string case for router compatibility
raw_prefix =
try do
PhoenixKit.Config.get_url_prefix()
rescue
# Fallback if config not available at compile time
_ -> "/phoenix_kit"
end
url_prefix =
case raw_prefix do
"" -> "/"
prefix -> prefix
end
# Use a generic locale pattern that accepts any valid language code format
# This allows switching to any of the 80+ predefined languages
# Actual validation of whether the locale is supported happens in the validation plug
pattern = "[a-z]{2}(?:-[A-Za-z0-9]{2,})?"
# Call route generators BEFORE quote block (aliases work in this context)
emails_routes = EmailsRoutes.generate(url_prefix)
publishing_routes = PublishingRoutes.generate(url_prefix)
tickets_routes = TicketsRoutes.generate(url_prefix)
shop_public_routes = ShopRoutes.generate_public_routes(url_prefix)
blog_routes = BlogRoutes.generate(url_prefix)
quote do
# Generate pipeline definitions
unquote(generate_pipelines())
# Generate basic routes scope
unquote(generate_basic_scope(url_prefix))
# Generate module routes from separate files (improves compilation time)
unquote(emails_routes)
unquote(publishing_routes)
unquote(tickets_routes)
# Generate localized routes
unquote(generate_localized_routes(url_prefix, pattern))
# Generate non-localized routes
unquote(generate_non_localized_routes(url_prefix))
# Generate shop public routes (with locale support)
unquote(shop_public_routes)
# Generate blog routes (after other routes to prevent conflicts)
unquote(blog_routes)
# Generate catch-all route for pages at root level (must be last)
unquote(generate_pages_catch_all())
end
end
@doc """
**DEPRECATED**: This macro is no longer needed.
The Sync WebSocket is now automatically handled via the router when you
use `phoenix_kit_routes()`. The websocket endpoint is available at
`{url_prefix}/sync/websocket` without any additional configuration.
You can safely remove this macro from your endpoint.ex if you have it.
## Legacy Usage (deprecated)
Previously, this macro was required in endpoint.ex:
defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
import PhoenixKitWeb.Integration
# No longer needed - remove this line
# phoenix_kit_socket()
end
## Implementation Note
WebSocket handling is now done via `forward` in the router, which makes
the setup fully self-contained within PhoenixKit. No endpoint modifications
are required.
"""
@deprecated "Sync websocket is now handled automatically via phoenix_kit_routes()"
defmacro phoenix_kit_socket do
quote do
plug PhoenixKit.Modules.Sync.Web.SocketPlug
end
end
def init(opts) do
opts
end
def call(conn, :phoenix_kit_auto_setup) do
# Add backward compatibility for layouts that use render_slot(@inner_block)
Plug.Conn.assign(conn, :inner_block, [])
end
end