diff --git a/config/config.exs b/config/config.exs index b6a8dd86..2ceb1e88 100644 --- a/config/config.exs +++ b/config/config.exs @@ -31,6 +31,27 @@ config :animina, :ai_message_help_price, 20 # Configures whether to have ai automatically respond to messages config :animina, :autoreply_messages_with_ai, false +# Configures default maximum age for potential partners +config :animina, :default_potential_partner_maximum_age, 100 + +# Configures default minimum age for potential partners +config :animina, :default_potential_partner_minimum_age, 18 + +# confugures the difference in age if no maximum or minimum age is set +config :animina, :default_partner_age_offset, 10 + +# Configures the maximum height for potential partners +config :animina, :default_potential_partner_maximum_height, 225 + +# Configures the minimum height for potential partners +config :animina, :default_potential_partner_minimum_height, 0 + +# Configures the maximum height difference for potential partners +config :animina, :default_partner_height_offset, 10 + +# Configures the default search range for potential partners in km +config :animina, :default_potential_partner_search_range_in_km, 10_000 + config :animina, ecto_repos: [Animina.Repo], generators: [timestamp_type: :utc_datetime] diff --git a/lib/animina_web/components/beta_registration_components.ex b/lib/animina_web/components/beta_registration_components.ex index fd2006ec..b7e737dc 100644 --- a/lib/animina_web/components/beta_registration_components.ex +++ b/lib/animina_web/components/beta_registration_components.ex @@ -28,7 +28,7 @@ defmodule AniminaWeb.BetaRegistrationComponents do

<%= with_locale(@language, fn -> %> - <%= gettext("Current Potential Partners:") %> 123 + <%= gettext("Current Potential Partners:") %> <%= @number_of_potential_partners %> <% end) %>

@@ -37,7 +37,7 @@ defmodule AniminaWeb.BetaRegistrationComponents do id="beta_user_registration_form" for={@form} class="space-y-6 mt-6 group " - phx-change="validate" + phx-change="validate_and_filter_potential_partners" phx-submit="submit" >

@@ -209,7 +209,7 @@ defmodule AniminaWeb.BetaRegistrationComponents do
- <%= text_input(@f, :height, + <%= number_input(@f, :height, class: "block w-full rounded-md border-0 py-1.5 text-gray-900 dark:bg-gray-700 dark:text-white shadow-sm ring-1 ring-inset placeholder:text-gray-400 focus:ring-2 focus:ring-inset sm:text-sm phx-no-feedback:ring-gray-300 phx-no-feedback:focus:ring-indigo-600 sm:leading-6 " <> unless(get_field_errors(@f[:height], :height) == [], @@ -319,28 +319,32 @@ defmodule AniminaWeb.BetaRegistrationComponents do ~H"""
-
- <%= select(@f, :minimum_partner_age, Enum.map(18..110, &{&1, &1}), - prompt: with_locale(@language, fn -> gettext("doesn't matter") end), - value: @f[:minimum_partner_age].value, +
+ <%= select( + @f, + :minimum_partner_height, + [{with_locale(@language, fn -> gettext("doesn't matter") end), nil}] ++ + Enum.map(140..210, &{"#{&1} cm", &1}), class: - "block w-full rounded-md border-0 py-1.5 text-gray-900 dark:bg-gray-700 dark:text-white shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset phx-no-feedback:ring-gray-300 phx-no-feedback:focus:ring-indigo-600 sm:text-sm sm:leading-6 " <> - unless(get_field_errors(@f[:minimum_partner_age], :minimum_partner_age) == [], + "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset focus:ring-2 dark:bg-gray-700 dark:text-white focus:ring-inset phx-no-feedback:ring-gray-300 phx-no-feedback:focus:ring-indigo-600 sm:text-sm sm:leading-6 " <> + unless( + get_field_errors(@f[:minimum_partner_height], :minimum_partner_height) == [], do: "ring-red-600 focus:ring-red-600", else: "ring-gray-300 focus:ring-indigo-600" - ) + ), + autofocus: true ) %> - <.error :for={msg <- get_field_errors(@f[:minimum_partner_age], :minimum_partner_age)}> + <.error :for={msg <- get_field_errors(@f[:minimum_partner_height], :minimum_partner_height)}> <%= with_locale(@language, fn -> %> - <%= gettext("Minimum age") <> " " <> msg %> + <%= gettext("Minimum height") <> " " <> msg %> <% end) %>
@@ -356,7 +360,7 @@ defmodule AniminaWeb.BetaRegistrationComponents do class="block text-sm font-medium leading-6 text-gray-900 dark:text-white" > <%= with_locale(@language, fn -> %> - <%= gettext("Maximum height") %> + <%= gettext("Maximum %{height}", height: "height") %> <% end) %>
@@ -392,7 +396,7 @@ defmodule AniminaWeb.BetaRegistrationComponents do class="block text-sm font-medium leading-6 text-gray-900 dark:text-white" > <%= with_locale(@language, fn -> %> - <%= gettext("Minimum age") %> + <%= gettext("Minimum %{age}", age: "age") %> <% end) %>
@@ -425,7 +429,7 @@ defmodule AniminaWeb.BetaRegistrationComponents do class="block text-sm font-medium leading-6 text-gray-900 dark:text-white" > <%= with_locale(@language, fn -> %> - <%= gettext("Maximum age") %> + <%= gettext("Maximum %{age}", age: "age") %> <% end) %>
diff --git a/lib/animina_web/live/beta_register.ex b/lib/animina_web/live/beta_register.ex index 67b1211d..c4cfa708 100644 --- a/lib/animina_web/live/beta_register.ex +++ b/lib/animina_web/live/beta_register.ex @@ -1,10 +1,14 @@ defmodule AniminaWeb.BetaRegisterLive do use AniminaWeb, :live_view alias Animina.Accounts.User + alias AniminaWeb.PotentialPartner alias AshPhoenix.Form @impl true def mount(_params, %{"language" => language} = _session, socket) do + potential_partners = + PotentialPartner.potential_partners_on_registration(default_user_params()) + socket = socket |> assign(language: language) @@ -12,6 +16,7 @@ defmodule AniminaWeb.BetaRegisterLive do |> assign(active_tab: "register") |> assign(trigger_action: false) |> assign(current_user_credit_points: 0) + |> assign(:number_of_potential_partners, Enum.count(potential_partners)) |> assign(:errors, []) |> assign( :form, @@ -21,11 +26,39 @@ defmodule AniminaWeb.BetaRegisterLive do {:ok, socket} end + @impl true + def handle_event("validate_and_filter_potential_partners", %{"user" => user}, socket) do + potential_partners = PotentialPartner.potential_partners_on_registration(user) + + {:noreply, + socket + |> assign(:number_of_potential_partners, Enum.count(potential_partners))} + end + + defp default_user_params do + %{ + "height" => "", + "maximum_partner_height" => "", + "minimum_partner_height" => "", + "maximum_partner_age" => "", + "minimum_partner_age" => "", + "gender" => "male", + "search_range" => "", + "zip_code" => "", + "birthday" => "" + } + end + @impl true def render(assigns) do ~H"""
- <.initial_form language={@language} form={@form} errors={@errors} /> + <.initial_form + number_of_potential_partners={@number_of_potential_partners} + language={@language} + form={@form} + errors={@errors} + />
""" end diff --git a/lib/animina_web/potential_partner.ex b/lib/animina_web/potential_partner.ex index 633c0424..9eb21155 100644 --- a/lib/animina_web/potential_partner.ex +++ b/lib/animina_web/potential_partner.ex @@ -5,7 +5,7 @@ defmodule AniminaWeb.PotentialPartner do alias Animina.Accounts alias Animina.Accounts.User - # alias Animina.GeoData.City + alias Animina.GeoData.City # alias Animina.Traits.UserFlags require Ash.Query @@ -52,6 +52,27 @@ defmodule AniminaWeb.PotentialPartner do |> Ash.read!() end + @doc """ + We use this to fetch potential partners for the user during registration. + """ + + def potential_partners_on_registration(user) do + User + |> Ash.Query.for_read(:read) + |> Ash.Query.sort(Ash.Sort.expr_sort(fragment("RANDOM()"))) + |> registration_partner_gender_query(user) + |> registration_partner_height_query(user) + |> registration_partner_age_query(user) + |> registration_partner_geo_query(user) + |> partner_completed_registration_query(user) + |> partner_not_under_investigation_query(user) + |> partner_not_banned_query(user) + |> partner_not_archived_query(user) + |> partner_not_hibernate_query(user) + |> partner_not_incognito_query(user) + |> Ash.read!() + end + defp partner_completed_registration_query(query, _user) do query |> Ash.Query.filter(not is_nil(registration_completed_at)) @@ -147,6 +168,116 @@ defmodule AniminaWeb.PotentialPartner do |> Ash.Query.filter(gender: [eq: user.partner_gender]) end + defp registration_partner_gender_query(query, user) do + if user["gender"] == "" do + query + else + query + |> Ash.Query.filter(gender: [eq: user["gender"]]) + end + end + + defp registration_partner_height_query(query, user) do + max_height = conditional_maximum_height(user["height"], user["maximum_partner_height"]) + min_height = conditional_minimum_height(user["height"], user["minimum_partner_height"]) + + query + |> Ash.Query.filter(height <= ^max_height) + |> Ash.Query.filter(height >= ^min_height) + end + + defp registration_partner_age_query(query, user) do + if user["birthday"] == "" do + query + else + age = calculate_age(convert_to_date(user["birthday"])) + + max_age = conditional_maximum_age(age, user["maximum_partner_age"]) + min_age = conditional_minimum_age(age, user["minimum_partner_age"]) + + query + |> Ash.Query.filter( + fragment( + "date_part('year', age(current_date, ?))", + birthday + ) >= ^min_age + ) + |> Ash.Query.filter( + fragment( + "date_part('year', age(current_date, ?))", + birthday + ) <= ^max_age + ) + end + end + + defp registration_partner_geo_query(query, user) do + if user["zip_code"] == "" do + query + else + nearby_zip_codes = + get_nearby_cities(user["zip_code"], user["search_range"]) + |> Enum.map(fn city -> city.zip_code end) + + query + |> Ash.Query.filter(zip_code: [in: nearby_zip_codes]) + end + end + + defp conditional_maximum_age("", "") do + Application.get_env(:animina, :default_potential_partner_maximum_age) + end + + defp conditional_maximum_age(age, "") do + age + Application.get_env(:animina, :default_partner_age_offset) + end + + defp conditional_maximum_age(_, max_age) do + String.to_integer(max_age) + end + + defp conditional_minimum_age("", "") do + Application.get_env(:animina, :default_potential_partner_minimum_age) + end + + defp conditional_minimum_age(age, "") do + age + Application.get_env(:animina, :default_partner_age_offset) + end + + defp conditional_minimum_age(_, min_age) do + String.to_integer(min_age) + end + + defp conditional_maximum_height("", "") do + # We set the minimum height to 0m if the user does not specify a minimum height + Application.get_env(:animina, :default_potential_partner_maximum_height) + end + + defp conditional_maximum_height(height, "") do + # We add a 10cm buffer to the user's height if the user does not specify a maximum height + String.to_integer(height) + + Application.get_env(:animina, :default_partner_height_offset) + end + + defp conditional_maximum_height(_height, maximum_partner_height) do + String.to_integer(maximum_partner_height) + end + + defp conditional_minimum_height("", "") do + # We set the minimum height to 0cm if the user does not specify a minimum height + Application.get_env(:animina, :default_potential_partner_minimum_height) + end + + defp conditional_minimum_height(height, "") do + # We remove a 10cm buffer to the user's height if the user does not specify a minimum height + String.to_integer(height) - + Application.get_env(:animina, :default_partner_height_offset) + end + + defp conditional_minimum_height(_, minimum_partner_height) do + String.to_integer(minimum_partner_height) + end + # defp partner_geo_query(query, user) do # nearby_zip_codes = # get_nearby_cities(user.zip_code, user.search_range) @@ -180,23 +311,35 @@ defmodule AniminaWeb.PotentialPartner do # We use the haversine formula to get locations # near the current city in the search range radius - # defp get_nearby_cities(zip_code, search_range) do - # current_city = City.by_zip_code!(zip_code) - # - # City - # |> Ash.Query.filter( - # fragment( - # "acos(sin(radians(?)) * sin(radians(lat)) + - # cos(radians(?)) * cos(radians(lat)) * - # cos(radians(?) - radians(lon))) * 6731 <= ?", - # ^current_city.lat, - # ^current_city.lat, - # ^current_city.lon, - # ^search_range - # ) - # ) - # |> Ash.read!() - # end + def get_nearby_cities(zip_code, search_range) do + search_range = + if search_range == "" do + Application.get_env(:animina, :default_potential_partner_search_range_in_km) + else + String.to_integer(search_range) + end + + case City.by_zip_code(zip_code) do + {:ok, current_city} -> + City + |> Ash.Query.filter( + fragment( + "acos(sin(radians(?)) * sin(radians(lat)) + + cos(radians(?)) * cos(radians(lat)) * + cos(radians(?) - radians(lon))) * 6731 <= ?", + ^current_city.lat, + ^current_city.lat, + ^current_city.lon, + ^search_range + ) + ) + |> Ash.read!() + + _ -> + # If the city is not found, we return an empty list + [] + end + end # defp get_user_flags(user_id, color) do # UserFlags @@ -213,4 +356,16 @@ defmodule AniminaWeb.PotentialPartner do [] end end + + def convert_to_date(date_string) do + {:ok, date} = Date.from_iso8601(date_string) + date + end + + def calculate_age(birthdate) do + today = Date.utc_today() + days = Date.diff(today, birthdate) + years = days / 365.25 + floor(years) + end end