diff --git a/lib/modules/sync/web/connections_live.ex b/lib/modules/sync/web/connections_live.ex index 1111486cd..4c2ab3538 100644 --- a/lib/modules/sync/web/connections_live.ex +++ b/lib/modules/sync/web/connections_live.ex @@ -1116,6 +1116,8 @@ defmodule PhoenixKit.Modules.Sync.Web.ConnectionsLive do end # Get all FK dependencies for a table (recursive) + @dialyzer {:nowarn_function, get_table_dependencies: 2} + @dialyzer {:nowarn_function, get_table_dependencies: 3} defp get_table_dependencies(table_name, tables) do get_table_dependencies(table_name, tables, MapSet.new()) |> MapSet.to_list() @@ -1159,6 +1161,7 @@ defmodule PhoenixKit.Modules.Sync.Web.ConnectionsLive do topo_sort(graph) end + @dialyzer {:nowarn_function, topo_sort: 4} defp topo_sort(graph) do topo_sort(graph, Map.keys(graph), [], MapSet.new()) end @@ -1174,6 +1177,7 @@ defmodule PhoenixKit.Modules.Sync.Web.ConnectionsLive do end end + @dialyzer {:nowarn_function, visit_node: 5} defp visit_node(graph, node, sorted, visited, path) do if MapSet.member?(visited, node) do {sorted, visited} diff --git a/lib/phoenix_kit_web/live/components/user_settings.ex b/lib/phoenix_kit_web/live/components/user_settings.ex index ababf9e70..40dba7685 100644 --- a/lib/phoenix_kit_web/live/components/user_settings.ex +++ b/lib/phoenix_kit_web/live/components/user_settings.ex @@ -119,6 +119,8 @@ defmodule PhoenixKitWeb.Live.Components.UserSettings do CustomFields.list_user_accessible_field_definitions() end) |> assign_new(:last_uploaded_avatar_uuid, fn -> nil end) + |> assign_new(:show_email_form, fn -> false end) + |> assign_new(:show_password_form, fn -> false end) |> maybe_allow_upload() {:ok, socket} @@ -253,8 +255,11 @@ defmodule PhoenixKitWeb.Live.Components.UserSettings do socket end + # Custom fields are nested under profile_form[user][custom_fields] + custom_fields_data = get_in(params, ["profile_form", "user", "custom_fields"]) + merged_params = - case params["custom_fields"] do + case custom_fields_data do custom_fields when is_map(custom_fields) -> Map.put(user_params, "custom_fields", custom_fields) @@ -284,8 +289,11 @@ defmodule PhoenixKitWeb.Live.Components.UserSettings do %{"user" => user_params} = params user = socket.assigns.user + # Custom fields are nested under profile_form[user][custom_fields] + custom_fields_data = get_in(params, ["profile_form", "user", "custom_fields"]) + merged_params = - case params["custom_fields"] do + case custom_fields_data do custom_fields when is_map(custom_fields) -> existing_avatar = get_in(user.custom_fields, ["avatar_file_uuid"]) @@ -448,6 +456,14 @@ defmodule PhoenixKitWeb.Live.Components.UserSettings do {:noreply, cancel_upload(socket, :avatar, ref)} end + def handle_event("toggle_email_form", _params, socket) do + {:noreply, assign(socket, :show_email_form, not socket.assigns.show_email_form)} + end + + def handle_event("toggle_password_form", _params, socket) do + {:noreply, assign(socket, :show_password_form, not socket.assigns.show_password_form)} + end + # Private helpers defp check_timezone_mismatch(socket, selected_timezone) do @@ -638,530 +654,351 @@ defmodule PhoenixKitWeb.Live.Components.UserSettings do @impl Phoenix.LiveComponent def render(assigns) do ~H""" -
-
- <%!-- Left Column - Profile --%> -
- <%!-- Profile Information Card --%> - <%= if :profile in @sections do %> -
-
-

- <.icon name="hero-user" class="w-5 h-5" /> Profile Information -

- - <%!-- Success Message --%> - <%= if @profile_success_message do %> -
- <.icon name="hero-check" class="stroke-current shrink-0 h-5 w-5" /> - {@profile_success_message} -
- <% end %> - - <%!-- Avatar Upload Section --%> -
- - -
- <%!-- Avatar Preview --%> -
- <%= if get_in(@user.custom_fields, ["avatar_file_uuid"]) do %> - <% avatar_url = - PhoenixKit.Modules.Storage.URLSigner.signed_url( - get_in(@user.custom_fields, ["avatar_file_uuid"]), - "thumbnail" - ) %> - Avatar - <% else %> -
- - {String.upcase(String.at(@user.email, 0))} - -
- <% end %> -
- - <%!-- Upload Controls --%> -
- <.file_upload - upload={@uploads.avatar} - variant="button" - label="Choose Profile Picture" - /> - -

- Upload a profile picture (max 10MB). Accepts JPG, PNG, GIF. -

- - <%!-- Success Message --%> - <%= if @last_uploaded_avatar_uuid do %> -
- <.icon name="hero-check" class="stroke-current shrink-0 h-5 w-5" /> - Avatar uploaded successfully! -
- <% end %> - - <%!-- Avatar Error Message --%> - <%= if @avatar_error_message do %> -
- <.icon - name="hero-exclamation-triangle" - class="stroke-current shrink-0 h-5 w-5" - /> - {@avatar_error_message} -
- <% end %> +
+
+

+ <.icon name="hero-user-circle" class="w-6 h-6" /> Account Settings +

+ + <%!-- Profile Section --%> + <%= if :profile in @sections do %> +
+ <%!-- Profile Form with Avatar --%> + <.simple_form + for={@profile_form} + id={"#{@id}-profile-form"} + phx-submit="update_profile" + phx-change="validate_profile" + phx-target={@myself} + > +
+ <%!-- Avatar Section --%> +
+ <%= if get_in(@user.custom_fields, ["avatar_file_uuid"]) do %> + <% avatar_url = + PhoenixKit.Modules.Storage.URLSigner.signed_url( + get_in(@user.custom_fields, ["avatar_file_uuid"]), + "thumbnail" + ) %> + Avatar + <% else %> +
+ + {String.upcase(String.at(@user.email, 0))} +
-
- - <%!-- Divider after avatar section --%> -
+ <% end %> + <.file_upload + upload={@uploads.avatar} + variant="button" + label="Upload" + />
- <.simple_form - for={@profile_form} - id={"#{@id}-profile-form"} - phx-submit="update_profile" - phx-change="validate_profile" - phx-target={@myself} - > -
- <.input - field={@profile_form[:first_name]} - type="text" - label="First Name" - /> + <%!-- Name Fields --%> +
+ <.input + field={@profile_form[:first_name]} + type="text" + label="First Name" + /> + <.input + field={@profile_form[:last_name]} + type="text" + label="Last Name" + /> +
<.input - field={@profile_form[:last_name]} + field={@profile_form[:username]} type="text" - label="Last Name" + label="Username" /> +
- - <%!-- Custom Fields Section --%> - <%= if length(@custom_field_definitions) > 0 do %> -
Additional Information
- - <%= for field_def <- @custom_field_definitions do %> - <%= case field_def["type"] do %> - <% "text" -> %> - <.input - name={"custom_fields[#{field_def["key"]}]"} - type="text" - label={field_def["label"]} - value={ - get_in(@user.custom_fields, [field_def["key"]]) || - field_def["default"] - } - required={field_def["required"]} - /> - <% "textarea" -> %> - <.textarea - name={"custom_fields[#{field_def["key"]}]"} - label={field_def["label"]} - value={ - get_in(@user.custom_fields, [field_def["key"]]) || - field_def["default"] - } - required={field_def["required"]} - /> - <% "number" -> %> - <.input - name={"custom_fields[#{field_def["key"]}]"} - type="number" - label={field_def["label"]} - value={ - get_in(@user.custom_fields, [field_def["key"]]) || - field_def["default"] - } - required={field_def["required"]} - /> - <% "email" -> %> - <.input - name={"custom_fields[#{field_def["key"]}]"} - type="email" - label={field_def["label"]} - value={ - get_in(@user.custom_fields, [field_def["key"]]) || - field_def["default"] - } - required={field_def["required"]} - /> - <% "url" -> %> - <.input - name={"custom_fields[#{field_def["key"]}]"} - type="url" - label={field_def["label"]} - value={ - get_in(@user.custom_fields, [field_def["key"]]) || - field_def["default"] - } - required={field_def["required"]} - /> - <% "date" -> %> - <.input - name={"custom_fields[#{field_def["key"]}]"} - type="date" - label={field_def["label"]} - value={ - get_in(@user.custom_fields, [field_def["key"]]) || - field_def["default"] - } - required={field_def["required"]} - /> + <%= for field <- @custom_field_definitions do %> + <% field_name = "profile_form[user][custom_fields][#{field["key"]}]" %> + <% field_id = "profile_form_user_custom_fields_#{field["key"]}" %> + <% field_value = + get_in(@user.custom_fields, [field["key"]]) || field["default"] || "" %> +
+ + <%= case field["type"] do %> <% "select" -> %> - <.select - name={"custom_fields[#{field_def["key"]}]"} - label={field_def["label"]} - options={Enum.map(field_def["options"], &{&1, &1})} - value={ - get_in(@user.custom_fields, [field_def["key"]]) || - field_def["default"] - } - required={field_def["required"]} - /> + <% _ -> %> - <%!-- Fallback for unknown field types --%> - <.input - name={"custom_fields[#{field_def["key"]}]"} + <% end %> - <% end %> - <% end %> - -
- <.select - field={@profile_form[:user_timezone]} - label="Personal Timezone" - options={@timezone_options} - /> - - <%!-- Timezone Mismatch Warning --%> - <%= if assigns[:timezone_mismatch_warning] do %> -
- <.icon - name="hero-exclamation-triangle" - class="stroke-current shrink-0 h-5 w-5" - /> -
-
Timezone Mismatch Detected
-
- {@timezone_mismatch_warning} -
-
-
- <% end %> - - <%!-- Browser Timezone Info --%> - <%= if assigns[:browser_timezone_name] do %> -
- Browser detected: {@browser_timezone_name} ({@browser_timezone_offset}) -
- <% end %> - - <%!-- Debug button for timezone detection --%> -
- -
- Click if timezone detection isn't working automatically -
-
- <:actions> - <.button - phx-disable-with="Updating..." - class="btn-primary" - > - <.icon name="hero-user" class="w-4 h-4 mr-2" /> Update Profile - - - + <% end %> +
-
- <% end %> - - <%!-- Email Settings Card --%> - <%= if :email in @sections do %> -
-
-

- <.icon name="hero-envelope" class="w-5 h-5" /> Email Address -

-

Change your account email address

- - <%!-- Email Success Message --%> - <%= if @email_success_message do %> -
- <.icon name="hero-check" class="stroke-current shrink-0 h-5 w-5" /> - {@email_success_message} -
- <% end %> - - <%!-- Email Error Message --%> - <%= if @email_error_message do %> -
- <.icon name="hero-exclamation-triangle" class="stroke-current shrink-0 h-5 w-5" /> - {@email_error_message} -
- <% end %> - <.simple_form - for={@email_form} - id={"#{@id}-email-form"} - phx-submit="update_email" - phx-change="validate_email" - phx-target={@myself} - > - <.input - field={@email_form[:email]} - type="email" - label="Email" - required - /> - <.input - field={@email_form[:current_password]} - name="current_password" - id={"#{@id}-current-password-for-email"} - type="password" - label="Current password" - value={@email_form_current_password} - required - /> - <:actions> - <.button - phx-disable-with="Changing..." - class="btn-primary" - > - <.icon name="hero-envelope" class="w-4 h-4 mr-2" /> Change Email - - - -
+ <:actions> +
+ <.button phx-disable-with="Updating..." class="btn-primary"> + Update Profile + +
+ + +
+
+ <% end %> + + <%!-- Email Section --%> + <%= if :email in @sections do %> +
+
+

+ <.icon name="hero-envelope" class="w-5 h-5 text-primary" /> Email Address +

+
- <% end %> - - <%!-- Password Settings Card --%> - <%= if :password in @sections do %> -
-
-

- <.icon name="hero-lock-closed" class="w-5 h-5" /> Password -

-

Update your account password

- - <%!-- Password Success Message --%> - <%= if @password_success_message do %> -
- <.icon name="hero-check" class="stroke-current shrink-0 h-5 w-5" /> - {@password_success_message} -
- <% end %> - <%!-- Password Error Message --%> - <%= if @password_error_message do %> -
- <.icon name="hero-exclamation-triangle" class="stroke-current shrink-0 h-5 w-5" /> - {@password_error_message} -
- <% end %> +
{@current_email}
- <.simple_form - for={@password_form} - id={"#{@id}-password-form"} - action={Routes.path("/users/log-in?_action=password_updated")} - method="post" - phx-change="validate_password" - phx-submit="update_password" - phx-trigger-action={@trigger_submit} - phx-target={@myself} - > - - <.input - field={@password_form[:password]} - type="password" - label="New password" - required - /> - <.input - field={@password_form[:password_confirmation]} - type="password" - label="Confirm new password" - /> - <.input - field={@password_form[:current_password]} - name="current_password" - type="password" - label="Current password" - id={"#{@id}-current-password-for-password"} - value={@current_password} - required - /> - <:actions> - <.button - phx-disable-with="Changing..." - class="btn-primary" - > - <.icon name="hero-lock-closed" class="w-4 h-4 mr-2" /> Change Password - - - + <%= if @email_success_message do %> +
+ <.icon name="hero-check" class="stroke-current shrink-0 h-4 w-4" /> + {@email_success_message}
-
- <% end %> -
- - <%!-- Right Column --%> -
- <%!-- Connected Accounts Card --%> - <%= if :oauth in @sections and @oauth_available do %> -
-
-

- <.icon name="hero-link" class="w-5 h-5" /> Connected Accounts -

-

- Manage OAuth providers for quick sign-in -

- - <%!-- OAuth Success Message --%> - <%= if @oauth_success_message do %> -
- <.icon name="hero-check" class="stroke-current shrink-0 h-5 w-5" /> - {@oauth_success_message} + <% end %> + <%= if @email_error_message do %> +
+ <.icon name="hero-exclamation-triangle" class="stroke-current shrink-0 h-4 w-4" /> + {@email_error_message} +
+ <% end %> + + <%= if @show_email_form do %> + <.simple_form + for={@email_form} + id={"#{@id}-email-form"} + phx-submit="update_email" + phx-change="validate_email" + phx-target={@myself} + > + <.input + field={@email_form[:email]} + type="email" + label="New Email" + required + /> + <.input + field={@email_form[:current_password]} + name="current_password" + type="password" + label="Current Password" + value={@email_form_current_password} + required + /> + <:actions> +
+ <.button phx-disable-with="Changing..." class="btn-primary"> + Update Email +
- <% end %> + + + <% end %> +
+ <% end %> + +
+ + <%!-- Password Section --%> + <%= if :password in @sections do %> +
+
+

+ <.icon name="hero-lock-closed" class="w-5 h-5 text-primary" /> Password +

+ +
- <%!-- OAuth Error Message --%> - <%= if @oauth_error_message do %> -
- <.icon name="hero-exclamation-triangle" class="stroke-current shrink-0 h-5 w-5" /> - {@oauth_error_message} -
- <% end %> +
••••••••
- <%!-- Connected Providers List --%> - <%= if length(@oauth_providers) > 0 do %> -
-

Connected Providers

- <%= for provider <- @oauth_providers do %> -
-
- <%= case provider.provider do %> - <% "google" -> %> - <.icon name="hero-globe-alt" class="w-6 h-6" /> - <% "apple" -> %> - <.icon name="hero-device-phone-mobile" class="w-6 h-6" /> - <% "github" -> %> - <.icon name="hero-code-bracket" class="w-6 h-6" /> - <% _ -> %> - <.icon name="hero-link" class="w-6 h-6" /> - <% end %> -
-
- {format_provider_name(provider.provider)} -
-
- {provider.provider_email || @current_email} -
-
-
- -
- <% end %> -
- <% else %> -
- <.icon name="hero-information-circle" class="stroke-current shrink-0 h-5 w-5" /> - - You don't have any OAuth providers connected yet. Connect one for faster sign-in. - + <%= if @password_success_message do %> +
+ <.icon name="hero-check" class="stroke-current shrink-0 h-4 w-4" /> + {@password_success_message} +
+ <% end %> + <%= if @password_error_message do %> +
+ <.icon name="hero-exclamation-triangle" class="stroke-current shrink-0 h-4 w-4" /> + {@password_error_message} +
+ <% end %> + + <%= if @show_password_form do %> + <.simple_form + for={@password_form} + id={"#{@id}-password-form"} + action={Routes.path("/users/log-in?_action=password_updated")} + method="post" + phx-change="validate_password" + phx-submit="update_password" + phx-trigger-action={@trigger_submit} + phx-target={@myself} + > + + <.input + field={@password_form[:password]} + type="password" + label="New Password" + required + /> + <.input + field={@password_form[:password_confirmation]} + type="password" + label="Confirm New Password" + /> + <.input + field={@password_form[:current_password]} + name="current_password" + type="password" + label="Current Password" + value={@current_password} + required + /> + <:actions> +
+ <.button phx-disable-with="Changing..." class="btn-primary"> + Update Password +
- <% end %> - - <%!-- Available Providers to Connect --%> - <%= if length(@available_providers) > 0 do %> -
-

- Connect Additional Providers -

-
- <%= for provider <- @available_providers do %> - + + + <% end %> +
+ <% end %> + + <%!-- OAuth Section --%> + <%= if :oauth in @sections and @oauth_available do %> +
+

+ <.icon name="hero-link" class="w-5 h-5 text-primary" /> Connected Accounts +

+ + <%= if @oauth_success_message do %> +
+ <.icon name="hero-check" class="stroke-current shrink-0 h-4 w-4" /> + {@oauth_success_message} +
+ <% end %> + <%= if @oauth_error_message do %> +
+ <.icon name="hero-exclamation-triangle" class="stroke-current shrink-0 h-4 w-4" /> + {@oauth_error_message} +
+ <% end %> + + <%!-- Connected Providers --%> + <%= if length(@oauth_providers) > 0 do %> +
+ <%= for provider <- @oauth_providers do %> +
+
+ <%= case provider.provider do %> + <% "google" -> %> + <.icon name="hero-globe-alt" class="w-5 h-5" /> + <% "github" -> %> + <.icon name="hero-code-bracket" class="w-5 h-5" /> + <% _ -> %> + <.icon name="hero-link" class="w-5 h-5" /> <% end %> + {format_provider_name(provider.provider)}
+
<% end %> - - <%!-- Password Warning for OAuth-only Users --%> - <%= if length(@oauth_providers) > 0 and @user.hashed_password == nil do %> -
- <.icon name="hero-exclamation-triangle" class="stroke-current shrink-0 h-5 w-5" /> -
-
No Password Set
-
- You signed up using OAuth. Consider setting a password above as a backup sign-in method. -
-
-
+
+ <% end %> + + <%!-- Available Providers --%> + <%= if length(@available_providers) > 0 do %> +
+ <%= for provider <- @available_providers do %> + <% end %>
-
- <% end %> -
+ <% end %> +
+ <% end %>
"""