Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions lib/phoenix_kit/users/auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,8 @@ defmodule PhoenixKit.Users.Auth do
{:error, %Ecto.Changeset{}}
"""
def update_user_custom_fields(%User{} = user, custom_fields) when is_map(custom_fields) do
ensure_field_definitions_exist(custom_fields)

case user
|> User.custom_fields_changeset(%{custom_fields: custom_fields})
|> Repo.update() do
Expand All @@ -1436,6 +1438,60 @@ defmodule PhoenixKit.Users.Auth do
end
end

defp ensure_field_definitions_exist(custom_fields) when is_map(custom_fields) do
definitions = CustomFields.list_field_definitions()
existing_keys = definitions |> Enum.map(& &1["key"]) |> MapSet.new()
new_keys = custom_fields |> Map.keys() |> Enum.reject(&MapSet.member?(existing_keys, &1))

unless new_keys == [] do
next_position =
case definitions do
[] -> 1
defs -> (Enum.max_by(defs, &(&1["position"] || 0))["position"] || 0) + 1
end

Enum.each(Enum.with_index(new_keys, next_position), fn {key, pos} ->
label =
key
|> String.replace("_", " ")
|> String.split()
|> Enum.map_join(" ", &String.capitalize/1)

value = Map.get(custom_fields, key)

CustomFields.add_field_definition(%{
"key" => key,
"label" => label,
"type" => infer_field_type(value),
"enabled" => true,
"user_accessible" => false,
"position" => pos
})
end)
end
end

defp infer_field_type(value) when is_boolean(value), do: "boolean"
defp infer_field_type(value) when is_number(value), do: "number"

defp infer_field_type(value) when is_binary(value) do
cond do
String.match?(value, ~r/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i) ->
"uuid"

String.match?(value, ~r/^https?:\/\//) ->
"url"

String.match?(value, ~r/^[^\s]+@[^\s]+\.[^\s]+$/) ->
"email"

true ->
"text"
end
end

defp infer_field_type(_), do: "text"

@doc """
Updates both schema and custom fields in a single call.

Expand Down
13 changes: 12 additions & 1 deletion lib/phoenix_kit/users/custom_fields.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ defmodule PhoenixKit.Users.CustomFields do

@setting_key "custom_user_fields_definitions"

@supported_types ~w(text textarea number boolean date email url select radio checkbox)
@supported_types ~w(text textarea number boolean date email url uuid select radio checkbox)

# Field Definition Management

Expand Down Expand Up @@ -528,6 +528,17 @@ defmodule PhoenixKit.Users.CustomFields do

defp validate_type(%{"type" => "boolean"}, _value), do: {:error, "Must be true or false"}

defp validate_type(%{"type" => "uuid"} = _field_def, value) do
if String.match?(
to_string(value),
~r/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
) do
:ok
else
{:error, "Invalid UUID format"}
end
end

defp validate_type(%{"type" => "date"} = _field_def, value) do
case Date.from_iso8601(to_string(value)) do
{:ok, _date} -> :ok
Expand Down
52 changes: 22 additions & 30 deletions lib/phoenix_kit_web/live/components/user_settings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,23 @@ defmodule PhoenixKitWeb.Live.Components.UserSettings do
def handle_event("validate_profile", params, socket) do
%{"user" => user_params} = params

socket =
if params["_target"] == ["avatar"] do
entries = socket.assigns.uploads.avatar.entries

if entries != [] do
send_update_after(
__MODULE__,
%{id: socket.assigns.id, action: :check_avatar_uploads_complete},
500
)
end

socket
else
socket
end

socket =
case {params["browser_timezone_name"], params["browser_timezone_offset"]} do
{name, offset} when is_binary(name) and is_binary(offset) ->
Expand Down Expand Up @@ -292,25 +309,17 @@ defmodule PhoenixKitWeb.Live.Components.UserSettings do
# Custom fields are nested under profile_form[user][custom_fields]
custom_fields_data = get_in(params, ["profile_form", "user", "custom_fields"])

existing_custom_fields = user.custom_fields || %{}

merged_params =
case custom_fields_data do
custom_fields when is_map(custom_fields) ->
existing_avatar = get_in(user.custom_fields, ["avatar_file_uuid"])

updated_custom_fields =
if existing_avatar do
Map.put(custom_fields, "avatar_file_uuid", existing_avatar)
else
custom_fields
end

updated_custom_fields = Map.merge(existing_custom_fields, custom_fields)
Map.put(user_params, "custom_fields", updated_custom_fields)

_ ->
existing_avatar = get_in(user.custom_fields, ["avatar_file_uuid"])

if existing_avatar do
Map.put(user_params, "custom_fields", %{"avatar_file_uuid" => existing_avatar})
if map_size(existing_custom_fields) > 0 do
Map.put(user_params, "custom_fields", existing_custom_fields)
else
user_params
end
Expand Down Expand Up @@ -435,23 +444,6 @@ defmodule PhoenixKitWeb.Live.Components.UserSettings do
end
end

def handle_event("validate", %{"_target" => ["avatar"]}, socket) do
entries = socket.assigns.uploads.avatar.entries
Logger.info("avatar validate event: entries=#{length(entries)}")

if entries != [] do
Logger.info("avatar validate: scheduling check_uploads_complete")

send_update_after(
__MODULE__,
%{id: socket.assigns.id, action: :check_avatar_uploads_complete},
500
)
end

{:noreply, socket}
end

def handle_event("cancel_upload", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :avatar, ref)}
end
Expand Down
2 changes: 1 addition & 1 deletion lib/phoenix_kit_web/live/settings/users.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@
required
phx-change="field_type_changed"
>
<%= for type <- ~w(text textarea number boolean date email url select radio checkbox) do %>
<%= for type <- ~w(text textarea number boolean date email url uuid select radio checkbox) do %>
<option
value={type}
selected={@field_form_type == type}
Expand Down
8 changes: 8 additions & 0 deletions lib/phoenix_kit_web/users/user_form.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,14 @@
placeholder="https://example.com"
required={field_def["required"]}
/>
<% "uuid" -> %>
<input
type="text"
name={"user[custom_fields][#{field_key}]"}
value={field_value}
class={"input input-bordered w-full #{if field_error, do: "input-error", else: ""} font-mono text-sm"}
readonly
/>
<% "select" -> %>
<select
name={"user[custom_fields][#{field_key}]"}
Expand Down
Loading