Packages

phoenix_kit

1.7.21
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
phoenix_kit lib phoenix_kit_web live settings.ex
Raw

lib/phoenix_kit_web/live/settings.ex

defmodule PhoenixKitWeb.Live.Settings do
@moduledoc """
Admin settings management LiveView for PhoenixKit.
Provides interface for managing system-wide settings including timezone, date/time formats.
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Config
alias PhoenixKit.Modules.Languages
alias PhoenixKit.Settings
alias PhoenixKit.Settings.Events, as: SettingsEvents
alias PhoenixKit.Users.OAuthConfig
alias PhoenixKit.Utils.Date, as: UtilsDate
require Logger
@default_locale Config.default_locale()
def mount(_params, _session, socket) do
# Subscribe to settings changes for live updates (like entities does)
if connected?(socket) do
SettingsEvents.subscribe_to_settings()
end
# Load current settings from database
current_settings = Settings.list_all_settings()
defaults = Settings.get_defaults()
setting_options = Settings.get_setting_options()
# Merge defaults with current settings to ensure all keys exist
merged_settings = Map.merge(defaults, current_settings)
# Create form changeset
changeset = Settings.change_settings(merged_settings)
# Load module statuses
languages_enabled = Languages.enabled?()
# Load content language
content_language = Settings.get_content_language()
content_language_details = Settings.get_content_language_details()
# Get available languages if module is enabled
available_languages =
if languages_enabled do
Languages.get_enabled_languages()
else
[]
end
# Load admin languages from settings cache
# Default is [@default_locale] - a fresh install only has the default locale enabled
admin_languages_json =
Settings.get_setting_cached("admin_languages", nil) ||
Jason.encode!([@default_locale])
admin_languages =
case Jason.decode(admin_languages_json) do
{:ok, codes} when is_list(codes) -> codes
_ -> [@default_locale]
end
socket =
socket
|> assign(:page_title, "Settings")
|> assign(:settings, merged_settings)
# Track saved values separately
|> assign(:saved_settings, merged_settings)
|> assign(:setting_options, setting_options)
|> assign(:changeset, changeset)
|> assign(:saving, false)
|> assign(:project_title, merged_settings["project_title"] || "PhoenixKit")
|> assign(:languages_enabled, languages_enabled)
|> assign(:content_language, content_language)
|> assign(:content_language_details, content_language_details)
|> assign(:available_content_languages, available_languages)
|> assign(:admin_languages, admin_languages)
{:ok, socket}
end
def handle_params(_params, _url, socket) do
{:noreply, socket}
end
def handle_event("validate_settings", %{"settings" => settings_params}, socket) do
# Update the changeset with new values for validation
changeset = Settings.validate_settings(settings_params)
# Update the current settings to reflect the pending changes (but don't save to DB)
socket =
socket
|> assign(:settings, settings_params)
|> assign(:changeset, changeset)
{:noreply, socket}
end
def handle_event("save_settings", %{"settings" => settings_params}, socket) do
socket = assign(socket, :saving, true)
settings_params_to_save = prepare_settings_params(settings_params)
case Settings.update_settings(settings_params_to_save) do
{:ok, updated_settings} ->
handle_settings_saved(socket, settings_params_to_save, updated_settings)
{:error, errors} ->
handle_settings_error(socket, errors)
end
end
def handle_event("test_oauth", %{"provider" => provider}, socket) do
provider_atom = String.to_existing_atom(provider)
case OAuthConfig.test_connection(provider_atom) do
{:ok, message} ->
socket = put_flash(socket, :info, message)
{:noreply, socket}
{:error, message} ->
socket = put_flash(socket, :error, message)
{:noreply, socket}
end
end
def handle_event("reload_oauth_config", _params, socket) do
# Reload OAuth configuration from database
OAuthConfig.configure_providers()
socket = put_flash(socket, :info, "OAuth configuration reloaded from database")
{:noreply, socket}
end
def handle_event("reset_to_defaults", _params, socket) do
# Get default settings
defaults = Settings.get_defaults()
# Update all settings to defaults in database
case Settings.update_settings(defaults) do
{:ok, updated_settings} ->
# Also reset admin_languages to just the default locale
Settings.update_json_setting("admin_languages", [@default_locale])
# Update socket with default settings
changeset = Settings.change_settings(updated_settings)
socket =
socket
|> assign(:settings, updated_settings)
|> assign(:saved_settings, updated_settings)
|> assign(:changeset, changeset)
|> assign(:project_title, updated_settings["project_title"] || "PhoenixKit")
|> assign(:admin_languages, [@default_locale])
|> put_flash(:info, "Settings reset to defaults successfully")
{:noreply, socket}
{:error, errors} ->
error_msg = format_error_message(errors)
socket =
socket
|> put_flash(:error, error_msg)
{:noreply, socket}
end
end
def handle_event("update_content_language", %{"language" => language_code}, socket) do
case Settings.set_content_language(language_code) do
{:ok, _setting} ->
# Reload content language details
content_language_details = Settings.get_content_language_details()
socket =
socket
|> assign(:content_language, language_code)
|> assign(:content_language_details, content_language_details)
|> put_flash(:info, "Content language updated to #{content_language_details.name}")
{:noreply, socket}
{:error, reason} ->
socket = put_flash(socket, :error, reason)
{:noreply, socket}
end
end
def handle_event(
"add_admin_language_to_form",
%{"admin_language_code_input" => language_code},
socket
)
when language_code != "" do
current_languages = socket.assigns.admin_languages || [@default_locale]
# Toggle: add if not present, remove if already present
if language_code in current_languages do
# Remove language
updated_languages = Enum.filter(current_languages, &(&1 != language_code))
# Get language details for feedback
language = Languages.get_predefined_language(language_code)
language_name = if language, do: language.name, else: String.upcase(language_code)
socket =
socket
|> assign(:admin_languages, updated_languages)
|> put_flash(:info, "#{language_name} removed. Remember to save settings.")
{:noreply, socket}
else
# Add language
updated_languages = current_languages ++ [language_code]
# Get language details for feedback
language = Languages.get_predefined_language(language_code)
language_name = if language, do: language.name, else: String.upcase(language_code)
socket =
socket
|> assign(:admin_languages, updated_languages)
|> put_flash(:info, "#{language_name} added. Remember to save settings.")
{:noreply, socket}
end
end
def handle_event("add_admin_language_to_form", _params, socket) do
{:noreply, socket}
end
def handle_event("remove_admin_language_from_form", %{"code" => code}, socket) do
current_languages = socket.assigns.admin_languages || [@default_locale]
# Prevent removing the last language
if length(current_languages) <= 1 do
socket =
put_flash(
socket,
:warning,
"Cannot remove the last language. At least one language must be configured."
)
{:noreply, socket}
else
updated_languages = Enum.filter(current_languages, &(&1 != code))
# Get language details for feedback
language = Languages.get_predefined_language(code)
language_name = if language, do: language.name, else: String.upcase(code)
socket =
socket
|> assign(:admin_languages, updated_languages)
|> put_flash(:info, "#{language_name} removed. Remember to save settings.")
{:noreply, socket}
end
end
## Live updates - handle broadcasts from other admins
def handle_info({:content_language_changed, new_language}, socket) do
# Another admin changed the content language - update our view
content_language_details = Settings.get_content_language_details()
socket =
socket
|> assign(:content_language, new_language)
|> assign(:content_language_details, content_language_details)
{:noreply, socket}
end
def handle_info({:admin_languages_changed, admin_languages}, socket) do
# Another admin changed the admin languages - update our view
socket = assign(socket, :admin_languages, admin_languages)
{:noreply, socket}
end
# Catch-all for other settings changes (future-proof)
def handle_info({:setting_changed, _key, _value}, socket) do
{:noreply, socket}
end
# Handle successful settings save
defp handle_settings_saved(socket, settings_params_to_save, updated_settings) do
# Reload OAuth configuration to apply new credentials immediately
OAuthConfig.configure_providers()
# Broadcast admin languages change if it was updated
if Map.has_key?(settings_params_to_save, "admin_languages") do
admin_languages = parse_admin_languages_json(updated_settings["admin_languages"])
SettingsEvents.broadcast_admin_languages_changed(admin_languages)
end
# Update socket with new settings
changeset = Settings.change_settings(updated_settings)
saved_admin_languages =
extract_saved_admin_languages(settings_params_to_save, socket.assigns.admin_languages)
socket =
socket
|> assign(:settings, updated_settings)
|> assign(:saved_settings, updated_settings)
|> assign(:changeset, changeset)
|> assign(:saving, false)
|> assign(:project_title, updated_settings["project_title"] || "PhoenixKit")
|> assign(:admin_languages, saved_admin_languages)
|> put_flash(:info, "Settings updated successfully")
{:noreply, socket}
end
# Handle settings save error
defp handle_settings_error(socket, errors) do
Logger.error("Settings save error: #{inspect(errors)}")
error_msg = format_error_message(errors)
socket =
socket
|> assign(:saving, false)
|> put_flash(:error, error_msg)
{:noreply, socket}
end
# Parse admin_languages JSON string to list
# Default is [@default_locale] - a fresh install only has the default locale enabled
defp parse_admin_languages_json(json) when is_binary(json) do
if String.trim(json) != "" do
case Jason.decode(json) do
{:ok, codes} when is_list(codes) -> codes
_ -> [@default_locale]
end
else
[@default_locale]
end
end
defp parse_admin_languages_json(_), do: [@default_locale]
# Prepare settings params by handling admin_languages field
# Ensures admin_languages is never empty - defaults to [@default_locale]
defp prepare_settings_params(%{"admin_languages" => json} = params) when is_binary(json) do
trimmed = String.trim(json)
cond do
# Empty string - use default
trimmed == "" ->
Map.put(params, "admin_languages", Jason.encode!([@default_locale]))
# Empty JSON array - use default
trimmed == "[]" ->
Map.put(params, "admin_languages", Jason.encode!([@default_locale]))
# Valid JSON - keep it
true ->
params
end
end
defp prepare_settings_params(params), do: Map.delete(params, "admin_languages")
# Extract admin_languages from saved params or use fallback
defp extract_saved_admin_languages(settings_params, fallback) do
if Map.has_key?(settings_params, "admin_languages") do
parse_admin_languages_json(settings_params["admin_languages"])
else
fallback
end
end
# Format error messages for display
defp format_error_message(%Ecto.Changeset{} = changeset) do
# Extract error messages from changeset
changeset
|> Ecto.Changeset.traverse_errors(fn {msg, opts} ->
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end)
|> Map.values()
|> List.flatten()
|> Enum.join(", ")
end
# Helper functions for template to show dropdown labels
def get_timezone_label(value, setting_options) do
Settings.get_timezone_label(value, setting_options)
end
def get_option_label(value, options) do
Settings.get_option_label(value, options)
end
# Helper functions for template to show current format examples
def get_current_date_example(format) do
UtilsDate.format_date(Date.utc_today(), format)
end
def get_current_time_example(format) do
UtilsDate.format_time(Time.utc_now(), format)
end
# Helper function to generate OAuth callback URL
def get_oauth_callback_url(settings, provider) do
site_url = settings["site_url"] || "https://example.com"
url_prefix = PhoenixKit.Config.get_url_prefix()
"#{site_url}#{url_prefix}/users/auth/#{provider}/callback"
end
end