From 2cbce8ec39dfdce63f445205fac1db91859db085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= <49537445+JasterV@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:57:54 +0100 Subject: [PATCH] refactor: make phone number optional (#9) This pull request makes phone numbers optional for both user registrations and bookings, and updates validation, database schema, and UI to reflect this change. The main focus is to ensure that users are no longer required to provide a phone number, and that the application gracefully handles cases where a phone number is absent. **Database & Resource Model Updates** * Made the `phone_number` attribute in the `users` table and the `customer_phone` attribute in the `bookings` table nullable, including migration and resource snapshot updates. [[1]](diffhunk://#diff-baa6aed3674c4d6cbbebeafb076662df02dc4c25231dbd9dc9c8f0534ed1a1bfR1-R29) [[2]](diffhunk://#diff-a401f66b2ae5bfb798eb1bc2221bfeeac943e258950c90d59570b0bae05d3664R1-R244) [[3]](diffhunk://#diff-0c1180d6f6abc19b5987c8703bdee9ef67905535202f950e8327c32bd5b89d8aR1-R82) * Updated Ash resource definitions in `user.ex` and `booking.ex` to allow `phone_number` and `customer_phone` to be `nil`. [[1]](diffhunk://#diff-9194b9d80dce091f6dcb56f784217272ae160e35454c4b4ccc8850ad5ee06e38L152-R152) [[2]](diffhunk://#diff-4b1ddd6d86899f2144c69d142883b8719c755e32c03dbda5da2188208a5ad503L55-R55) [[3]](diffhunk://#diff-4b1ddd6d86899f2144c69d142883b8719c755e32c03dbda5da2188208a5ad503L170-R170) **Validation & Parsing Logic** * Renamed and refactored user registration field validation to `ParseRegistrationFields`, allowing phone numbers to be omitted and trimming input values. Empty phone numbers are now treated as absent rather than as errors. [[1]](diffhunk://#diff-8ffdd76e260e3cda6f0816c8e585ae76b993a90d2519c38185a5fe22b4b49e47L1-R1) [[2]](diffhunk://#diff-8ffdd76e260e3cda6f0816c8e585ae76b993a90d2519c38185a5fe22b4b49e47R14-R60) * Updated the authentication callback logic to trim input values and omit the phone number parameter if it is blank. **User Interface Improvements** * Updated registration and booking forms to indicate that phone numbers are optional, removed the required attribute, and improved placeholder text. [[1]](diffhunk://#diff-f356eb84970d8c9ee6ff1992c297b0cae07bade37ff967c1e6e0de6f8b67081cL101-R115) [[2]](diffhunk://#diff-43c0e1f7a869ee5c43a911bc10dc80cbb265a8672340ef0fa7c1d3009c047f02L92-R92) * Updated email templates and confirmation screens to display "N/A" or "-" when phone numbers are missing. [[1]](diffhunk://#diff-48468ef2d1bb2c33b5ffb40457b77532815c7faf1830932661f665bff58b2177R6-R11) [[2]](diffhunk://#diff-3f33187b4021450b481ce53fe13166addea582c627f2cfbc99c75c7ce5c34857L10-R10) [[3]](diffhunk://#diff-43c0e1f7a869ee5c43a911bc10dc80cbb265a8672340ef0fa7c1d3009c047f02L92-R92) **Profile Management** * Improved profile update flow to ensure the form reflects the latest user data after saving changes. * Made the "Full Name" field explicitly required in the profile form UI. --- .credo.exs | 2 +- lib/spazio_solazzo/accounts/user.ex | 4 +- .../changes/validate_registration_fields.ex | 45 +++- lib/spazio_solazzo/booking_system/booking.ex | 4 +- .../admin_notification.html.heex | 7 +- .../customer_confirmation.html.heex | 2 +- .../live/auth/auth_callback_live.ex | 15 +- .../live/auth/auth_callback_live.html.heex | 8 +- .../live/booking/asset_booking_live.ex | 8 +- .../booking/booking_form_live_component.ex | 96 +++++-- .../live/user/profile_live.ex | 6 +- .../live/user/profile_live.html.heex | 5 +- ...115004442_set_phone_number_as_nullable.exs | 29 +++ .../repo/bookings/20260115004442.json | 244 ++++++++++++++++++ .../repo/users/20260115004442.json | 82 ++++++ test/spazio_solazzo/accounts/user_test.exs | 30 +-- .../booking_system/booking_test.exs | 12 +- .../controllers/booking_controller_test.exs | 9 +- .../live/booking_live/asset_booking_test.exs | 136 ++++++++-- .../live/user/profile_live_test.exs | 24 +- test/support/auth_helpers.ex | 64 +++++ test/support/conn_case.ex | 3 + test/support/data_case.ex | 1 + 23 files changed, 699 insertions(+), 137 deletions(-) create mode 100644 priv/repo/migrations/20260115004442_set_phone_number_as_nullable.exs create mode 100644 priv/resource_snapshots/repo/bookings/20260115004442.json create mode 100644 priv/resource_snapshots/repo/users/20260115004442.json create mode 100644 test/support/auth_helpers.ex diff --git a/.credo.exs b/.credo.exs index f806174..5526a45 100644 --- a/.credo.exs +++ b/.credo.exs @@ -89,7 +89,7 @@ # If you don't want TODO comments to cause `mix credo` to fail, just # set this value to 0 (zero). # - {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagTODO, [exit_status: 0]}, # ## Readability Checks diff --git a/lib/spazio_solazzo/accounts/user.ex b/lib/spazio_solazzo/accounts/user.ex index 23d7d3b..27af03c 100644 --- a/lib/spazio_solazzo/accounts/user.ex +++ b/lib/spazio_solazzo/accounts/user.ex @@ -83,7 +83,7 @@ defmodule SpazioSolazzo.Accounts.User do change AshAuthentication.Strategy.MagicLink.SignInChange # Conditionally validate name and phone_number for new users - change SpazioSolazzo.Accounts.User.Changes.ValidateRegistrationFields + change SpazioSolazzo.Accounts.User.Changes.ParseRegistrationFields change {AshAuthentication.Strategy.RememberMe.MaybeGenerateTokenChange, strategy_name: :remember_me} @@ -149,7 +149,7 @@ defmodule SpazioSolazzo.Accounts.User do end attribute :phone_number, :string do - allow_nil? false + allow_nil? true public? true end end diff --git a/lib/spazio_solazzo/accounts/user/changes/validate_registration_fields.ex b/lib/spazio_solazzo/accounts/user/changes/validate_registration_fields.ex index 80466bc..88501ea 100644 --- a/lib/spazio_solazzo/accounts/user/changes/validate_registration_fields.ex +++ b/lib/spazio_solazzo/accounts/user/changes/validate_registration_fields.ex @@ -1,4 +1,4 @@ -defmodule SpazioSolazzo.Accounts.User.Changes.ValidateRegistrationFields do +defmodule SpazioSolazzo.Accounts.User.Changes.ParseRegistrationFields do @moduledoc """ Conditionally validates that name and phone_number are present for new user registrations. For existing users (upserts), these fields are not required. @@ -11,28 +11,53 @@ defmodule SpazioSolazzo.Accounts.User.Changes.ValidateRegistrationFields do case SpazioSolazzo.Accounts.get_user_by_email(email, authorize?: false) do {:ok, %{phone_number: phone, name: name}} -> + # User is already registered, we'll just set the same values it had changeset |> Ash.Changeset.force_change_attribute(:name, name) |> Ash.Changeset.force_change_attribute(:phone_number, phone) _ -> + # User is not yet registered, we'll parse & validate the new values name = Ash.Changeset.get_argument(changeset, :name) phone = Ash.Changeset.get_argument(changeset, :phone_number) changeset - |> validate_required_for_registration(:name, name) - |> validate_required_for_registration(:phone_number, phone) + |> parse_name(name) + |> parse_phone_number(phone) end end - defp validate_required_for_registration(changeset, field, value) do - if is_nil(value) || value == "" do - Ash.Changeset.add_error( - changeset, - Ash.Error.Changes.Required.exception(field: field, type: :argument) - ) + defp parse_name(changeset, nil) do + Ash.Changeset.add_error( + changeset, + Ash.Error.Changes.Required.exception(field: :name, type: :argument) + ) + end + + defp parse_name(changeset, value) do + value = String.trim(value) + + if value == "" do + parse_name(changeset, nil) else - Ash.Changeset.change_attribute(changeset, field, value) + Ash.Changeset.change_attribute(changeset, :name, value) + end + end + + defp parse_phone_number(changeset, nil) do + # The phone number is nullable, this is fine + Ash.Changeset.change_attribute(changeset, :phone_number, nil) + end + + defp parse_phone_number(changeset, value) do + value = String.trim(value) + + if value == "" do + # Instead of returning an error, we'll consider an empty phone number + # as if the user didn't want to set one, which is valid. + parse_name(changeset, nil) + else + Ash.Changeset.change_attribute(changeset, :phone_number, value) end end end diff --git a/lib/spazio_solazzo/booking_system/booking.ex b/lib/spazio_solazzo/booking_system/booking.ex index 8e99b58..8545d94 100644 --- a/lib/spazio_solazzo/booking_system/booking.ex +++ b/lib/spazio_solazzo/booking_system/booking.ex @@ -52,7 +52,7 @@ defmodule SpazioSolazzo.BookingSystem.Booking do argument :date, :date, allow_nil?: false argument :customer_name, :string, allow_nil?: false argument :customer_email, :string, allow_nil?: false - argument :customer_phone, :string, allow_nil?: false + argument :customer_phone, :string, allow_nil?: true argument :customer_comment, :string, allow_nil?: true change manage_relationship(:time_slot_template_id, :time_slot_template, @@ -167,7 +167,7 @@ defmodule SpazioSolazzo.BookingSystem.Booking do attribute :customer_email, :string, allow_nil?: false attribute :start_time, :time, allow_nil?: false attribute :end_time, :time, allow_nil?: false - attribute :customer_phone, :string, allow_nil?: false + attribute :customer_phone, :string, allow_nil?: true attribute :customer_comment, :string, allow_nil?: true attribute :state, :atom do diff --git a/lib/spazio_solazzo_web/emails/email_templates/admin_notification.html.heex b/lib/spazio_solazzo_web/emails/email_templates/admin_notification.html.heex index 0747713..5a52af4 100644 --- a/lib/spazio_solazzo_web/emails/email_templates/admin_notification.html.heex +++ b/lib/spazio_solazzo_web/emails/email_templates/admin_notification.html.heex @@ -3,7 +3,12 @@

Customer: {@customer_name}

Email: {@customer_email}

-

Phone: {@customer_phone}

+ + <%= if @customer_phone && String.trim(@customer_phone) != "" do %> +

Phone: {@customer_phone}

+ <% else %> +

Phone: N/A

+ <% end %>
<.details_list> diff --git a/lib/spazio_solazzo_web/emails/email_templates/customer_confirmation.html.heex b/lib/spazio_solazzo_web/emails/email_templates/customer_confirmation.html.heex index 4e6bab4..436db04 100644 --- a/lib/spazio_solazzo_web/emails/email_templates/customer_confirmation.html.heex +++ b/lib/spazio_solazzo_web/emails/email_templates/customer_confirmation.html.heex @@ -7,7 +7,7 @@ <.detail_item label="Date">{@date} <.detail_item label="Time">{@start_time} - {@end_time} <.detail_item label="Email">{@customer_email} - <.detail_item label="Phone">{@customer_phone} + <.detail_item label="Phone">{@customer_phone || "N/A"} <.detail_item label="Note">{@customer_comment || "N/A"} diff --git a/lib/spazio_solazzo_web/live/auth/auth_callback_live.ex b/lib/spazio_solazzo_web/live/auth/auth_callback_live.ex index 6e0190d..937d8dc 100644 --- a/lib/spazio_solazzo_web/live/auth/auth_callback_live.ex +++ b/lib/spazio_solazzo_web/live/auth/auth_callback_live.ex @@ -74,12 +74,17 @@ defmodule SpazioSolazzoWeb.AuthCallbackLive do ) do %{token: token} = socket.assigns remember_me = Map.get(args, "remember_me") == "on" + name = String.trim(name) + phone_number = String.trim(phone_number) - {:noreply, - redirect(socket, - to: - ~p"/auth/magic/sign-in?token=#{token}&name=#{name}&phone_number=#{phone_number}&remember_me=#{remember_me}" - )} + url = + if phone_number == "" do + ~p"/auth/magic/sign-in?token=#{token}&name=#{name}&remember_me=#{remember_me}" + else + ~p"/auth/magic/sign-in?token=#{token}&name=#{name}&phone_number=#{phone_number}&remember_me=#{remember_me}" + end + + {:noreply, redirect(socket, to: url)} end defp extract_email_from_token(token) do diff --git a/lib/spazio_solazzo_web/live/auth/auth_callback_live.html.heex b/lib/spazio_solazzo_web/live/auth/auth_callback_live.html.heex index 85f2aae..7644a33 100644 --- a/lib/spazio_solazzo_web/live/auth/auth_callback_live.html.heex +++ b/lib/spazio_solazzo_web/live/auth/auth_callback_live.html.heex @@ -98,21 +98,23 @@ for="phone_number" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2" > - Phone Number * + Phone Number (Optional)
<.icon name="hero-phone" class="size-5 text-slate-400 dark:text-slate-500" />
+

+ Your number will only be used to contact you personally about booking issues, never for marketing. +