diff --git a/lib/atomic/accounts.ex b/lib/atomic/accounts.ex index 0fd35f28e..dd2877564 100644 --- a/lib/atomic/accounts.ex +++ b/lib/atomic/accounts.ex @@ -115,6 +115,35 @@ defmodule Atomic.Accounts do end end + @doc """ + Return the first and last name of a name. + + ## Examples + + iex> extract_first_last_name("John Doe") + "John Doe" + + iex> extract_first_last_name("John") + "John" + + iex> extract_first_last_name(nil) + "" + + """ + def extract_first_last_name(name) do + names = + name + |> String.split(" ") + |> Enum.filter(&String.match?(String.slice(&1, 0, 1), ~r/^\p{L}$/u)) + |> Enum.map(&String.capitalize/1) + + case length(names) do + 0 -> "" + 1 -> hd(names) + _ -> List.first(names) <> " " <> List.last(names) + end + end + @doc """ Returns an `%Ecto.Changeset{}` for tracking user changes. diff --git a/lib/atomic_web.ex b/lib/atomic_web.ex index c08964aab..06b431091 100644 --- a/lib/atomic_web.ex +++ b/lib/atomic_web.ex @@ -98,7 +98,7 @@ defmodule AtomicWeb do import AtomicWeb.ErrorHelpers import AtomicWeb.Gettext - import AtomicWeb.UtilsView + import AtomicWeb.ViewUtils alias AtomicWeb.Router.Helpers, as: Routes alias Icons.{Heroicons, Ionicons} diff --git a/lib/atomic_web/live/activity_live/index.ex b/lib/atomic_web/live/activity_live/index.ex index f015369f8..8c5bc62b0 100644 --- a/lib/atomic_web/live/activity_live/index.ex +++ b/lib/atomic_web/live/activity_live/index.ex @@ -1,6 +1,7 @@ defmodule AtomicWeb.ActivityLive.Index do use AtomicWeb, :live_view + alias Atomic.Accounts alias Atomic.Activities alias Atomic.Activities.Activity diff --git a/lib/atomic_web/live/activity_live/index.html.heex b/lib/atomic_web/live/activity_live/index.html.heex index eacafb63d..d5bd032e1 100644 --- a/lib/atomic_web/live/activity_live/index.html.heex +++ b/lib/atomic_web/live/activity_live/index.html.heex @@ -49,7 +49,7 @@

<%= if hd(activity.activity_sessions) do %> - <%= display_date(hd(activity.activity_sessions).start) %> + <%= AtomicWeb.ViewUtils.display_date(hd(activity.activity_sessions).start) %> <% end %>

<%= if not is_nil(hd(activity.activity_sessions).location) do %> @@ -71,11 +71,11 @@
- <%= extract_initials(speaker.name) %> + <%= Accounts.extract_initials(speaker.name) %>

- <%= extract_first_last_name(speaker.name) %> + <%= Accounts.extract_first_last_name(speaker.name) %>

<% end %> diff --git a/lib/atomic_web/live/activity_live/show.ex b/lib/atomic_web/live/activity_live/show.ex index da92e20df..48c2e1b49 100644 --- a/lib/atomic_web/live/activity_live/show.ex +++ b/lib/atomic_web/live/activity_live/show.ex @@ -1,6 +1,7 @@ defmodule AtomicWeb.ActivityLive.Show do use AtomicWeb, :live_view + alias Atomic.Accounts alias Atomic.Activities alias Atomic.Organizations diff --git a/lib/atomic_web/live/activity_live/show.html.heex b/lib/atomic_web/live/activity_live/show.html.heex index 573d61e2f..21647815a 100644 --- a/lib/atomic_web/live/activity_live/show.html.heex +++ b/lib/atomic_web/live/activity_live/show.html.heex @@ -79,7 +79,7 @@

- <%= display_date(session.start) %> + <%= AtomicWeb.ViewUtils.display_date(session.start) %>

@@ -125,11 +125,11 @@
- <%= extract_initials(speaker.name) %> + <%= Accounts.extract_initials(speaker.name) %> <%= live_redirect to: Routes.speaker_show_path(@socket, :show, speaker), class: "text-md text-blue-500" do %> - <%= extract_first_last_name(speaker.name) %> + <%= Accounts.extract_first_last_name(speaker.name) %> <% end %>
diff --git a/lib/atomic_web/templates/layout/live.html.heex b/lib/atomic_web/templates/layout/live.html.heex index 959330f84..e810c6c0e 100644 --- a/lib/atomic_web/templates/layout/live.html.heex +++ b/lib/atomic_web/templates/layout/live.html.heex @@ -18,7 +18,7 @@ - <%= extract_initials("CeSIUM") %> + <%= Atomic.Accounts.extract_initials("CeSIUM") %> @@ -60,7 +60,7 @@ - <%= extract_initials("NECC") %> + <%= Atomic.Accounts.extract_initials("NECC") %> @@ -178,7 +178,7 @@ <% end %> <%= live_redirect to: Routes.user_settings_path(@socket, :edit), class: "bg-zinc-200 flex items-center gap-x-4 px-6 py-3 text-sm font-semibold leading-6 text-gray-900" do %> - <%= extract_initials(@current_user.email) %> + <%= Atomic.Accounts.extract_initials(@current_user.email) %> <% end %> diff --git a/lib/atomic_web/views/helpers.ex b/lib/atomic_web/views/helpers.ex index 475c2f5ff..e3db37af3 100644 --- a/lib/atomic_web/views/helpers.ex +++ b/lib/atomic_web/views/helpers.ex @@ -4,66 +4,145 @@ defmodule AtomicWeb.ViewUtils do """ use Phoenix.HTML + import AtomicWeb.Gettext + + alias Timex.Format.DateTime.Formatters.Relative + require Timex.Translator def frontend_url do Application.fetch_env!(:atomic, AtomicWeb.Endpoint)[:frontend_url] end - @doc """ - Display a user's name + @doc ~S""" + Returns a relative datetime string for the given datetime. + ## Examples - iex> display_name(%{first_name: "John", last_name: "Doe"}) - "John Doe" + + iex> relative_datetime(~N[2020-01-01 00:00:00]) + "3 years ago" + + iex> relative_datetime(~N[2023-01-01 00:00:00] |> Timex.shift(days: 1)) + "6 months ago" + """ - def display_name(user) do - "#{user.first_name} #{user.last_name}" + def relative_datetime(nil), do: "" + + def relative_datetime(""), do: "" + + def relative_datetime(datetime) do + Relative.lformat!(datetime, "{relative}", Gettext.get_locale()) end - @doc """ - Display a date in format "HH:MM" + @doc ~S""" + Returns a relative date string for the given date. + ## Examples - iex> display_time(~U[2018-01-01 00:00:00Z]) - "00:00" - iex> display_time(~U[2018-01-01 12:00:00Z]) - "12:00" - iex> display_time(~U[2018-01-01 23:59:00Z]) - "23:59" + + iex> display_date(~D[2020-01-01]) + "01-01-2020" + + iex> display_date(~D[2023-01-01]) + "01-01-2023" + """ - def display_time(%DateTime{} = datetime) do - hour = two_characters(datetime.hour) - minute = two_characters(datetime.minute) + def display_date(nil), do: "" + + def display_date(""), do: "" + + def display_date(date) when is_binary(date) do + date + |> Timex.parse!("%FT%H:%M", :strftime) + |> Timex.format!("{0D}-{0M}-{YYYY}") + end - "#{hour}:#{minute}" + def display_date(date) do + Timex.format!(date, "{0D}-{0M}-{YYYY}") end - @doc """ - Display a date in a given locale + @doc ~S""" + Returns a relative time string for the given time. + ## Examples - iex> display_date(~N[2021-03-10 02:27:07], "pt") - "Quarta-feira, 10 de Março de 2021" - iex> display_date(~N[2023-02-25 22:25:46], "en") - "Saturday, February 25, 2023" + + iex> display_time(~T[00:00:00]) + "00:00" + + iex> display_time(~T[23:59:59]) + "23:59" """ - def display_date(datetime, locale \\ "pt") + def display_time(nil), do: "" - def display_date(datetime, "pt" = locale) do - Timex.Translator.with_locale locale do - Timex.format!(datetime, "{WDfull}, {D} de {Mfull} de {YYYY}") - end + def display_time(""), do: "" + + def display_time(date) when is_binary(date) do + date + |> Timex.parse!("%FT%H:%M", :strftime) + |> Timex.format!("{0D}-{0M}-{YYYY}") end - def display_date(datetime, "en" = locale) do - Timex.Translator.with_locale locale do - Timex.format!(datetime, "{WDfull}, {Mfull} {D}, {YYYY}") - end + def display_time(date) do + date + |> Timex.format!("{h24}:{m}") end - defp two_characters(number) do - if number < 10 do - "0#{number}" + @doc ~S""" + Returns a list of first element from tuples where the second element is true + + ## Examples + + iex> class_list([{"col-start-1", true}, {"col-start-2", false}, {"col-start-3", true}]) + "col-start-1 col-start-3" + + iex> class_list([{"Math", true}, {"Physics", false}, {"Chemistry", false}]) + "Math" + """ + def class_list(items) do + items + |> Enum.reject(&(elem(&1, 1) == false)) + |> Enum.map_join(" ", &elem(&1, 0)) + end + + @doc ~S""" + Returns the class name for a given column + + ## Examples + + iex> col_start(1) + "col-start-1" + + iex> col_start(2) + "col-start-2" + + iex> col_start(0) + "col-start-0" + + iex> col_start(8) + "col-start-0" + """ + def col_start(col) do + if col in 1..7 do + "col-start-#{col}" else - number + "col-start-0" end end + + @doc ~S""" + Returns an error message for a given error + + ## Examples + + iex> error_to_string(:too_large) + "Too large" + + iex> error_to_string(:not_accepted) + "You have selected an unacceptable file type" + + iex> error_to_string(:too_many_files) + "You have selected too many files" + """ + def error_to_string(:too_large), do: gettext("Too large") + def error_to_string(:not_accepted), do: gettext("You have selected an unacceptable file type") + def error_to_string(:too_many_files), do: gettext("You have selected too many files") end diff --git a/lib/atomic_web/views/utils_view.ex b/lib/atomic_web/views/utils_view.ex deleted file mode 100644 index 61111374f..000000000 --- a/lib/atomic_web/views/utils_view.ex +++ /dev/null @@ -1,112 +0,0 @@ -defmodule AtomicWeb.UtilsView do - @moduledoc """ - Utility functions to be used on all views. - """ - use Phoenix.HTML - - import AtomicWeb.Gettext - - alias Timex.Format.DateTime.Formatters.Relative - - @spec extract_initials(nil | String.t()) :: String.t() - def extract_initials(nil), do: "" - - def extract_initials(name) do - initials = - name - |> String.upcase() - |> String.split(" ") - |> Enum.map(&String.slice(&1, 0, 1)) - |> Enum.filter(&String.match?(&1, ~r/^\p{L}$/u)) - - case length(initials) do - 0 -> "" - 1 -> hd(initials) - _ -> List.first(initials) <> List.last(initials) - end - end - - @spec extract_first_last_name(nil | String.t()) :: String.t() - def extract_first_last_name(nil), do: "" - - def extract_first_last_name(name) do - names = - name - |> String.split(" ") - |> Enum.filter(&String.match?(String.slice(&1, 0, 1), ~r/^\p{L}$/u)) - |> Enum.map(&String.capitalize/1) - - case length(names) do - 0 -> "" - 1 -> hd(names) - _ -> List.first(names) <> " " <> List.last(names) - end - end - - def relative_datetime(nil), do: "" - - def relative_datetime(""), do: "" - - def relative_datetime(datetime) do - Relative.lformat!(datetime, "{relative}", Gettext.get_locale()) - end - - def display_date(nil), do: "" - - def display_date(""), do: "" - - def display_date(date) when is_binary(date) do - date - |> Timex.parse!("%FT%H:%M", :strftime) - |> Timex.format!("{0D}-{0M}-{YYYY}") - end - - def display_date(date) do - Timex.format!(date, "{0D}-{0M}-{YYYY}") - end - - def display_time(nil), do: "" - - def display_time(""), do: "" - - def display_time(date) when is_binary(date) do - date - |> Timex.parse!("%FT%H:%M", :strftime) - |> Timex.format!("{0D}-{0M}-{YYYY}") - end - - def display_time(date) do - date - |> Timex.format!("{h24}:{m}") - end - - def class_list(items) do - items - |> Enum.reject(&(elem(&1, 1) == false)) - |> Enum.map_join(" ", &elem(&1, 0)) - end - - def col_start(col) do - case col do - 1 -> "col-start-1" - 2 -> "col-start-2" - 3 -> "col-start-3" - 4 -> "col-start-4" - 5 -> "col-start-5" - 6 -> "col-start-6" - 7 -> "col-start-7" - _ -> "col-start-0" - end - end - - def build_path(current_path, params) do - current_path - |> URI.parse() - |> Map.put(:query, URI.encode_query(params)) - |> URI.to_string() - end - - def error_to_string(:too_large), do: gettext("Too large") - def error_to_string(:not_accepted), do: gettext("You have selected an unacceptable file type") - def error_to_string(:too_many_files), do: gettext("You have selected too many files") -end diff --git a/test/atomic/utils_test.exs b/test/atomic/utils_test.exs new file mode 100644 index 000000000..a6d1b0526 --- /dev/null +++ b/test/atomic/utils_test.exs @@ -0,0 +1,5 @@ +defmodule Atomic.UtilsTest do + use ExUnit.Case, async: true + import AtomicWeb.ViewUtils + doctest AtomicWeb.ViewUtils +end