Current section
Files
Jump to
Current section
Files
lib/phoenix_kit_referrals/web/form.ex
defmodule PhoenixKitReferrals.Web.Form do
@moduledoc """
Referral code form LiveView for PhoenixKit admin panel.
Provides form interface for creating and editing user referral codes.
"""
use PhoenixKitWeb, :live_view
require Logger
alias PhoenixKit.Settings
alias PhoenixKit.Users.Auth
alias PhoenixKitReferrals, as: Referrals
alias PhoenixKitReferrals.Paths
def mount(params, _session, socket) do
code_uuid = params["code_uuid"]
mode = if code_uuid, do: :edit, else: :new
# Get project title from settings
project_title = Settings.get_project_title()
socket =
socket
|> assign(:mode, mode)
|> assign(:code_uuid, code_uuid)
|> assign(:page_title, page_title(mode))
|> assign(:project_title, project_title)
|> assign(:search_results, [])
|> assign(:selected_beneficiary, nil)
|> load_code_data(mode, code_uuid)
|> load_form_data()
{:ok, socket}
end
def handle_params(_params, uri, socket) do
{:noreply, assign(socket, :url_path, URI.parse(uri).path)}
end
def handle_event("validate_code", params, socket) do
# Extract referrals params (matches form name from changeset)
code_params = Map.get(params, "referrals", %{})
# Add beneficiary if selected (dual-write both integer and UUID)
updated_params =
case socket.assigns.selected_beneficiary do
nil ->
code_params
beneficiary ->
code_params
|> Map.put("beneficiary_uuid", beneficiary.uuid)
end
# Create changeset for validation
changeset =
case socket.assigns.mode do
:new -> Referrals.changeset(%Referrals{}, updated_params)
:edit -> Referrals.changeset(socket.assigns.code, updated_params)
end
|> Map.put(:action, :validate)
socket =
socket
|> assign(:changeset, changeset)
{:noreply, socket}
end
def handle_event("save_code", params, socket) do
# Extract referrals params (matches form name from changeset)
code_params = Map.get(params, "referrals", %{})
# Ensure beneficiary is included if selected (dual-write both integer and UUID)
updated_code_params =
case socket.assigns.selected_beneficiary do
nil ->
code_params
beneficiary ->
code_params
|> Map.put("beneficiary_uuid", beneficiary.uuid)
end
case socket.assigns.mode do
:new -> create_code(socket, updated_code_params)
:edit -> update_code(socket, updated_code_params)
end
end
def handle_event("generate_code", _params, socket) do
random_code = Referrals.generate_random_code()
# Get current changeset changes and add the generated code
current_changes = socket.assigns.changeset.changes
updated_changes = Map.put(current_changes, :code, random_code)
# Add beneficiary if selected
final_changes =
case socket.assigns.selected_beneficiary do
nil ->
updated_changes
beneficiary ->
updated_changes
|> Map.put(:beneficiary_uuid, beneficiary.uuid)
end
changeset =
case socket.assigns.mode do
:new -> Referrals.changeset(%Referrals{}, final_changes)
:edit -> Referrals.changeset(socket.assigns.code, final_changes)
end
socket =
socket
|> assign(:changeset, changeset)
{:noreply, socket}
end
def handle_event("search_beneficiary", %{"search" => search_term}, socket) do
search_results =
if String.length(search_term) >= 2 do
Auth.search_users(search_term)
else
[]
end
socket =
socket
|> assign(:search_results, search_results)
{:noreply, socket}
end
def handle_event("select_beneficiary", %{"user_uuid" => user_uuid}, socket) do
# Find the selected user from search results
selected_user =
Enum.find(socket.assigns.search_results, fn user ->
user.uuid == user_uuid
end)
# Update the changeset with the selected beneficiary, preserving other changes
current_changes = socket.assigns.changeset.changes
updated_changes =
current_changes
|> Map.put(:beneficiary_uuid, if(selected_user, do: selected_user.uuid))
changeset =
case socket.assigns.mode do
:new -> Referrals.changeset(%Referrals{}, updated_changes)
:edit -> Referrals.changeset(socket.assigns.code, updated_changes)
end
socket =
socket
|> assign(:changeset, changeset)
|> assign(:selected_beneficiary, selected_user)
|> assign(:search_results, [])
{:noreply, socket}
end
def handle_event("clear_beneficiary", _params, socket) do
# Clear the beneficiary selection while preserving other changes
current_changes = socket.assigns.changeset.changes
updated_changes = Map.delete(current_changes, :beneficiary)
changeset =
case socket.assigns.mode do
:new -> Referrals.changeset(%Referrals{}, updated_changes)
:edit -> Referrals.changeset(socket.assigns.code, updated_changes)
end
socket =
socket
|> assign(:changeset, changeset)
|> assign(:selected_beneficiary, nil)
|> assign(:search_results, [])
{:noreply, socket}
end
def handle_event("cancel", _params, socket) do
{:noreply, push_navigate(socket, to: Paths.index())}
end
# Private functions
defp load_code_data(socket, :new, _code_uuid) do
assign(socket, :code, nil)
end
defp load_code_data(socket, :edit, code_uuid) do
code = Referrals.get_code!(code_uuid)
assign(socket, :code, code)
end
defp load_form_data(socket) do
code = socket.assigns.code || %Referrals{}
# For new codes, initialize with empty changeset
# For edit mode, initialize changeset with current code data to pre-populate form
initial_params =
case socket.assigns.mode do
:new ->
%{}
:edit ->
%{
"code" => code.code,
"description" => code.description,
"max_uses" => code.max_uses,
"expiration_date" => code.expiration_date,
"status" => code.status
}
end
changeset = Referrals.changeset(code, initial_params)
# Load selected beneficiary if editing existing code with beneficiary UUID
selected_beneficiary =
case code.beneficiary_uuid do
nil -> nil
beneficiary_uuid -> Auth.get_user_for_selection(beneficiary_uuid)
end
socket
|> assign(:changeset, changeset)
|> assign(:selected_beneficiary, selected_beneficiary)
end
defp create_code(socket, code_params) do
{code_params_with_creator, user_uuid} = extract_user_info(socket, code_params)
socket
|> create_code_with_validation(code_params_with_creator, user_uuid)
|> then(&{:noreply, &1})
end
defp extract_user_info(socket, code_params) do
case socket.assigns.phoenix_kit_current_user do
user when not is_nil(user) ->
params =
code_params
|> Map.put("created_by_uuid", user.uuid)
{params, user.uuid}
_ ->
extract_user_from_scope(socket, code_params)
end
end
defp extract_user_from_scope(socket, code_params) do
case socket.assigns do
%{phoenix_kit_current_scope: %{user_uuid: user_uuid}} when not is_nil(user_uuid) ->
params =
code_params
|> Map.put("created_by_uuid", user_uuid)
{params, user_uuid}
_ ->
Logger.warning("Socket assigns when current_user is nil: #{inspect(socket.assigns)}")
{code_params, nil}
end
end
defp create_code_with_validation(socket, code_params_with_creator, user_uuid) do
case validate_user_limit(user_uuid) do
{:ok, :valid} -> do_create_code(socket, code_params_with_creator)
{:error, limit_message} -> put_flash(socket, :error, limit_message)
nil -> do_create_code(socket, code_params_with_creator)
end
end
defp validate_user_limit(nil), do: nil
defp validate_user_limit(user_uuid), do: Referrals.validate_user_code_limit(user_uuid)
defp do_create_code(socket, code_params_with_creator) do
case Referrals.create_code(code_params_with_creator) do
{:ok, _code} ->
socket
|> put_flash(:info, "Referral created successfully!")
|> push_navigate(to: Paths.index())
{:error, changeset} ->
socket
|> assign(:changeset, changeset)
|> put_flash(:error, "Failed to create referral. Please check the errors below.")
end
end
defp update_code(socket, code_params) do
case Referrals.update_code(socket.assigns.code, code_params) do
{:ok, _code} ->
socket
|> put_flash(:info, "Referral updated successfully!")
|> push_navigate(to: Paths.index())
{:error, changeset} ->
socket
|> assign(:changeset, changeset)
|> put_flash(:error, "Failed to update referral. Please check the errors below.")
end
|> then(&{:noreply, &1})
end
defp page_title(:new), do: "New Referral"
defp page_title(:edit), do: "Edit Referral"
end