update controllers and auth
This commit is contained in:
		
							
								
								
									
										23
									
								
								lib/lokal_web/controllers/email_controller.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								lib/lokal_web/controllers/email_controller.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| defmodule LokalWeb.EmailController do | ||||
|   @moduledoc """ | ||||
|   A dev controller used to develop on emails | ||||
|   """ | ||||
|  | ||||
|   use LokalWeb, :controller | ||||
|   alias Lokal.Accounts.User | ||||
|  | ||||
|   plug :put_layout, {LokalWeb.LayoutView, :email} | ||||
|  | ||||
|   @sample_assigns %{ | ||||
|     email: %{subject: "Example subject"}, | ||||
|     url: "https://lokal.bubbletea.dev/sample_url", | ||||
|     user: %User{email: "sample@email.com"} | ||||
|   } | ||||
|  | ||||
|   @doc """ | ||||
|   Debug route used to preview emails | ||||
|   """ | ||||
|   def preview(conn, %{"id" => template}) do | ||||
|     render(conn, "#{template |> to_string()}.html", @sample_assigns) | ||||
|   end | ||||
| end | ||||
| @@ -1,12 +1,13 @@ | ||||
| defmodule LokalWeb.UserAuth do | ||||
|   @moduledoc """ | ||||
|   Module for any user authentication functions | ||||
|   Functions for user session and authentication | ||||
|   """ | ||||
|  | ||||
|   import Plug.Conn | ||||
|   import Phoenix.Controller | ||||
|   import LokalWeb.Gettext | ||||
|   alias Lokal.{Accounts, Accounts.User} | ||||
|   alias LokalWeb.PageLive | ||||
|   alias LokalWeb.Router.Helpers, as: Routes | ||||
|  | ||||
|   # Make the remember me cookie valid for 60 days. | ||||
| @@ -32,6 +33,7 @@ defmodule LokalWeb.UserAuth do | ||||
|  | ||||
|   def log_in_user(conn, %User{confirmed_at: nil}, _params) do | ||||
|     conn | ||||
|     |> fetch_flash() | ||||
|     |> put_flash( | ||||
|       :error, | ||||
|       dgettext("errors", "You must confirm your account and log in to access this page.") | ||||
| @@ -53,6 +55,11 @@ defmodule LokalWeb.UserAuth do | ||||
|     |> redirect(to: user_return_to || signed_in_path(conn)) | ||||
|   end | ||||
|  | ||||
|   @spec maybe_write_remember_me_cookie( | ||||
|           Plug.Conn.t(), | ||||
|           String.t() | any(), | ||||
|           %{required(String.t()) => String.t()} | any() | ||||
|         ) :: Plug.Conn.t() | ||||
|   defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do | ||||
|     put_resp_cookie(conn, @remember_me_cookie, token, @remember_me_options) | ||||
|   end | ||||
| @@ -149,13 +156,31 @@ defmodule LokalWeb.UserAuth do | ||||
|       conn | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, "You must confirm your account and log in to access this page.") | ||||
|       |> put_flash( | ||||
|         :error, | ||||
|         dgettext("errors", "You must confirm your account and log in to access this page.") | ||||
|       ) | ||||
|       |> maybe_store_return_to() | ||||
|       |> redirect(to: Routes.user_session_path(conn, :new)) | ||||
|       |> halt() | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   @doc """ | ||||
|   Used for routes that require the user to be an admin. | ||||
|   """ | ||||
|   def require_role(conn, role: role_atom) do | ||||
|     if conn.assigns[:current_user] && conn.assigns.current_user.role == role_atom do | ||||
|       conn | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, dgettext("errors", "You are not authorized to view this page.")) | ||||
|       |> maybe_store_return_to() | ||||
|       |> redirect(to: Routes.live_path(conn, PageLive)) | ||||
|       |> halt() | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   defp maybe_store_return_to(%{method: "GET"} = conn) do | ||||
|     put_session(conn, :user_return_to, current_path(conn)) | ||||
|   end | ||||
|   | ||||
| @@ -1,10 +1,11 @@ | ||||
| defmodule LokalWeb.UserConfirmationController do | ||||
|   use LokalWeb, :controller | ||||
|  | ||||
|   import LokalWeb.Gettext | ||||
|   alias Lokal.Accounts | ||||
|  | ||||
|   def new(conn, _params) do | ||||
|     render(conn, "new.html") | ||||
|     render(conn, "new.html", page_title: gettext("Confirm your account")) | ||||
|   end | ||||
|  | ||||
|   def create(conn, %{"user" => %{"email" => email}}) do | ||||
| @@ -19,8 +20,11 @@ defmodule LokalWeb.UserConfirmationController do | ||||
|     conn | ||||
|     |> put_flash( | ||||
|       :info, | ||||
|       "If your email is in our system and it has not been confirmed yet, " <> | ||||
|         "you will receive an email with instructions shortly." | ||||
|       dgettext( | ||||
|         "prompts", | ||||
|         "If your email is in our system and it has not been confirmed yet, " <> | ||||
|           "you will receive an email with instructions shortly." | ||||
|       ) | ||||
|     ) | ||||
|     |> redirect(to: "/") | ||||
|   end | ||||
| @@ -29,9 +33,9 @@ defmodule LokalWeb.UserConfirmationController do | ||||
|   # leaked token giving the user access to the account. | ||||
|   def confirm(conn, %{"token" => token}) do | ||||
|     case Accounts.confirm_user(token) do | ||||
|       {:ok, _} -> | ||||
|       {:ok, %{email: email}} -> | ||||
|         conn | ||||
|         |> put_flash(:info, "User confirmed successfully.") | ||||
|         |> put_flash(:info, dgettext("prompts", "%{email} confirmed successfully.", email: email)) | ||||
|         |> redirect(to: "/") | ||||
|  | ||||
|       :error -> | ||||
| @@ -45,7 +49,10 @@ defmodule LokalWeb.UserConfirmationController do | ||||
|  | ||||
|           %{} -> | ||||
|             conn | ||||
|             |> put_flash(:error, "User confirmation link is invalid or it has expired.") | ||||
|             |> put_flash( | ||||
|               :error, | ||||
|               dgettext("errors", "User confirmation link is invalid or it has expired.") | ||||
|             ) | ||||
|             |> redirect(to: "/") | ||||
|         end | ||||
|     end | ||||
|   | ||||
| @@ -1,30 +1,81 @@ | ||||
| defmodule LokalWeb.UserRegistrationController do | ||||
|   use LokalWeb, :controller | ||||
|  | ||||
|   alias Lokal.Accounts | ||||
|   import LokalWeb.Gettext | ||||
|   alias Lokal.{Accounts, Invites} | ||||
|   alias Lokal.Accounts.User | ||||
|   alias LokalWeb.UserAuth | ||||
|   alias LokalWeb.{Endpoint, PageLive} | ||||
|  | ||||
|   def new(conn, _params) do | ||||
|     changeset = Accounts.change_user_registration(%User{}) | ||||
|     render(conn, "new.html", changeset: changeset) | ||||
|   def new(conn, %{"invite" => invite_token}) do | ||||
|     invite = Invites.get_invite_by_token(invite_token) | ||||
|  | ||||
|     if invite do | ||||
|       conn |> render_new(invite) | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired")) | ||||
|       |> redirect(to: Routes.live_path(Endpoint, PageLive)) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def create(conn, %{"user" => user_params}) do | ||||
|   def new(conn, _params) do | ||||
|     if Accounts.allow_registration?() do | ||||
|       conn |> render_new() | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, dgettext("errors", "Sorry, public registration is disabled")) | ||||
|       |> redirect(to: Routes.live_path(Endpoint, PageLive)) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   # renders new user registration page | ||||
|   defp render_new(conn, invite \\ nil) do | ||||
|     render(conn, "new.html", | ||||
|       changeset: Accounts.change_user_registration(%User{}), | ||||
|       invite: invite, | ||||
|       page_title: gettext("Register") | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def create(conn, %{"user" => %{"invite_token" => invite_token}} = attrs) do | ||||
|     invite = Invites.get_invite_by_token(invite_token) | ||||
|  | ||||
|     if invite do | ||||
|       conn |> create_user(attrs, invite) | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired")) | ||||
|       |> redirect(to: Routes.live_path(Endpoint, PageLive)) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def create(conn, attrs) do | ||||
|     if Accounts.allow_registration?() do | ||||
|       conn |> create_user(attrs) | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, dgettext("errors", "Sorry, public registration is disabled")) | ||||
|       |> redirect(to: Routes.live_path(Endpoint, PageLive)) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   defp create_user(conn, %{"user" => user_params}, invite \\ nil) do | ||||
|     case Accounts.register_user(user_params) do | ||||
|       {:ok, user} -> | ||||
|         {:ok, _} = | ||||
|           Accounts.deliver_user_confirmation_instructions( | ||||
|             user, | ||||
|             &Routes.user_confirmation_url(conn, :confirm, &1) | ||||
|           ) | ||||
|         unless invite |> is_nil() do | ||||
|           invite |> Invites.use_invite!() | ||||
|         end | ||||
|  | ||||
|         Accounts.deliver_user_confirmation_instructions( | ||||
|           user, | ||||
|           &Routes.user_confirmation_url(conn, :confirm, &1) | ||||
|         ) | ||||
|  | ||||
|         conn | ||||
|         |> put_flash(:info, "User created successfully.") | ||||
|         |> UserAuth.log_in_user(user) | ||||
|         |> put_flash(:info, dgettext("prompts", "Please check your email to verify your account")) | ||||
|         |> redirect(to: Routes.user_session_path(Endpoint, :new)) | ||||
|  | ||||
|       {:error, %Ecto.Changeset{} = changeset} -> | ||||
|         render(conn, "new.html", changeset: changeset) | ||||
|         conn |> render("new.html", changeset: changeset, invite: invite) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -6,7 +6,7 @@ defmodule LokalWeb.UserResetPasswordController do | ||||
|   plug :get_user_by_reset_password_token when action in [:edit, :update] | ||||
|  | ||||
|   def new(conn, _params) do | ||||
|     render(conn, "new.html") | ||||
|     render(conn, "new.html", page_title: gettext("Forgot your password?")) | ||||
|   end | ||||
|  | ||||
|   def create(conn, %{"user" => %{"email" => email}}) do | ||||
| @@ -21,13 +21,20 @@ defmodule LokalWeb.UserResetPasswordController do | ||||
|     conn | ||||
|     |> put_flash( | ||||
|       :info, | ||||
|       "If your email is in our system, you will receive instructions to reset your password shortly." | ||||
|       dgettext( | ||||
|         "prompts", | ||||
|         "If your email is in our system, you will receive instructions to " <> | ||||
|           "reset your password shortly." | ||||
|       ) | ||||
|     ) | ||||
|     |> redirect(to: "/") | ||||
|   end | ||||
|  | ||||
|   def edit(conn, _params) do | ||||
|     render(conn, "edit.html", changeset: Accounts.change_user_password(conn.assigns.user)) | ||||
|     render(conn, "edit.html", | ||||
|       changeset: Accounts.change_user_password(conn.assigns.user), | ||||
|       page_title: gettext("Reset your password") | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   # Do not log in the user after reset password to avoid a | ||||
| @@ -36,7 +43,7 @@ defmodule LokalWeb.UserResetPasswordController do | ||||
|     case Accounts.reset_user_password(conn.assigns.user, user_params) do | ||||
|       {:ok, _} -> | ||||
|         conn | ||||
|         |> put_flash(:info, "Password reset successfully.") | ||||
|         |> put_flash(:info, dgettext("prompts", "Password reset successfully.")) | ||||
|         |> redirect(to: Routes.user_session_path(conn, :new)) | ||||
|  | ||||
|       {:error, changeset} -> | ||||
| @@ -51,7 +58,10 @@ defmodule LokalWeb.UserResetPasswordController do | ||||
|       conn |> assign(:user, user) |> assign(:token, token) | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, "Reset password link is invalid or it has expired.") | ||||
|       |> put_flash( | ||||
|         :error, | ||||
|         dgettext("errors", "Reset password link is invalid or it has expired.") | ||||
|       ) | ||||
|       |> redirect(to: "/") | ||||
|       |> halt() | ||||
|     end | ||||
|   | ||||
| @@ -5,7 +5,7 @@ defmodule LokalWeb.UserSessionController do | ||||
|   alias LokalWeb.UserAuth | ||||
|  | ||||
|   def new(conn, _params) do | ||||
|     render(conn, "new.html", error_message: nil) | ||||
|     render(conn, "new.html", error_message: nil, page_title: gettext("Log in")) | ||||
|   end | ||||
|  | ||||
|   def create(conn, %{"user" => user_params}) do | ||||
| @@ -14,13 +14,13 @@ defmodule LokalWeb.UserSessionController do | ||||
|     if user = Accounts.get_user_by_email_and_password(email, password) do | ||||
|       UserAuth.log_in_user(conn, user, user_params) | ||||
|     else | ||||
|       render(conn, "new.html", error_message: "Invalid email or password") | ||||
|       render(conn, "new.html", error_message: dgettext("errors", "Invalid email or password")) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def delete(conn, _params) do | ||||
|     conn | ||||
|     |> put_flash(:info, "Logged out successfully.") | ||||
|     |> put_flash(:info, dgettext("prompts", "Logged out successfully.")) | ||||
|     |> UserAuth.log_out_user() | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| defmodule LokalWeb.UserSettingsController do | ||||
|   use LokalWeb, :controller | ||||
|  | ||||
|   import LokalWeb.Gettext | ||||
|   alias Lokal.Accounts | ||||
|   alias LokalWeb.UserAuth | ||||
|   alias LokalWeb.{PageLive, UserAuth} | ||||
|  | ||||
|   plug :assign_email_and_password_changesets | ||||
|  | ||||
|   def edit(conn, _params) do | ||||
|     render(conn, "edit.html") | ||||
|     render(conn, "edit.html", page_title: gettext("Settings")) | ||||
|   end | ||||
|  | ||||
|   def update(conn, %{"action" => "update_email"} = params) do | ||||
| @@ -25,7 +25,10 @@ defmodule LokalWeb.UserSettingsController do | ||||
|         conn | ||||
|         |> put_flash( | ||||
|           :info, | ||||
|           "A link to confirm your email change has been sent to the new address." | ||||
|           dgettext( | ||||
|             "prompts", | ||||
|             "A link to confirm your email change has been sent to the new address." | ||||
|           ) | ||||
|         ) | ||||
|         |> redirect(to: Routes.user_settings_path(conn, :edit)) | ||||
|  | ||||
| @@ -41,7 +44,7 @@ defmodule LokalWeb.UserSettingsController do | ||||
|     case Accounts.update_user_password(user, password, user_params) do | ||||
|       {:ok, user} -> | ||||
|         conn | ||||
|         |> put_flash(:info, "Password updated successfully.") | ||||
|         |> put_flash(:info, dgettext("prompts", "Password updated successfully.")) | ||||
|         |> put_session(:user_return_to, Routes.user_settings_path(conn, :edit)) | ||||
|         |> UserAuth.log_in_user(user) | ||||
|  | ||||
| @@ -54,16 +57,33 @@ defmodule LokalWeb.UserSettingsController do | ||||
|     case Accounts.update_user_email(conn.assigns.current_user, token) do | ||||
|       :ok -> | ||||
|         conn | ||||
|         |> put_flash(:info, "Email changed successfully.") | ||||
|         |> put_flash(:info, dgettext("prompts", "Email changed successfully.")) | ||||
|         |> redirect(to: Routes.user_settings_path(conn, :edit)) | ||||
|  | ||||
|       :error -> | ||||
|         conn | ||||
|         |> put_flash(:error, "Email change link is invalid or it has expired.") | ||||
|         |> put_flash( | ||||
|           :error, | ||||
|           dgettext("errors", "Email change link is invalid or it has expired.") | ||||
|         ) | ||||
|         |> redirect(to: Routes.user_settings_path(conn, :edit)) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def delete(%{assigns: %{current_user: current_user}} = conn, %{"id" => user_id}) do | ||||
|     if user_id == current_user.id do | ||||
|       current_user |> Accounts.delete_user!(current_user) | ||||
|  | ||||
|       conn | ||||
|       |> put_flash(:error, dgettext("prompts", "Your account has been deleted")) | ||||
|       |> redirect(to: Routes.live_path(conn, PageLive)) | ||||
|     else | ||||
|       conn | ||||
|       |> put_flash(:error, dgettext("errors", "Unable to delete user")) | ||||
|       |> redirect(to: Routes.user_settings_path(conn, :edit)) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   defp assign_email_and_password_changesets(conn, _opts) do | ||||
|     user = conn.assigns.current_user | ||||
|  | ||||
|   | ||||
| @@ -4,22 +4,31 @@ defmodule LokalWeb.ErrorHelpers do | ||||
|   """ | ||||
|  | ||||
|   use Phoenix.HTML | ||||
|   import Phoenix.LiveView.Helpers | ||||
|   alias Ecto.Changeset | ||||
|   alias Phoenix.{HTML.Form, LiveView.Rendered} | ||||
|  | ||||
|   @doc """ | ||||
|   Generates tag for inlined form input errors. | ||||
|   """ | ||||
|   def error_tag(form, field) do | ||||
|     Enum.map(Keyword.get_values(form.errors, field), fn error -> | ||||
|       content_tag(:span, translate_error(error), | ||||
|         class: "invalid-feedback", | ||||
|         phx_feedback_for: input_name(form, field) | ||||
|       ) | ||||
|     end) | ||||
|   @spec error_tag(Form.t(), Form.field()) :: Rendered.t() | ||||
|   @spec error_tag(Form.t(), Form.field(), String.t()) :: Rendered.t() | ||||
|   def error_tag(form, field, extra_class \\ "") do | ||||
|     assigns = %{extra_class: extra_class, form: form, field: field} | ||||
|  | ||||
|     ~H""" | ||||
|     <%= for error <- Keyword.get_values(@form.errors, @field) do %> | ||||
|       <span class={"invalid-feedback #{@extra_class}"} phx-feedback-for={input_name(@form, @field)}> | ||||
|         <%= translate_error(error) %> | ||||
|       </span> | ||||
|     <% end %> | ||||
|     """ | ||||
|   end | ||||
|  | ||||
|   @doc """ | ||||
|   Translates an error message using gettext. | ||||
|   """ | ||||
|   @spec translate_error({String.t(), keyword() | map()}) :: String.t() | ||||
|   def translate_error({msg, opts}) do | ||||
|     # When using gettext, we typically pass the strings we want | ||||
|     # to translate as a static argument: | ||||
| @@ -44,4 +53,30 @@ defmodule LokalWeb.ErrorHelpers do | ||||
|       Gettext.dgettext(LokalWeb.Gettext, "errors", msg, opts) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   @doc """ | ||||
|   Displays all errors from a changeset, or just for a single key | ||||
|   """ | ||||
|   @spec changeset_errors(Changeset.t()) :: String.t() | ||||
|   @spec changeset_errors(Changeset.t(), key :: atom()) :: [String.t()] | nil | ||||
|   def changeset_errors(changeset) do | ||||
|     changeset | ||||
|     |> changeset_error_map() | ||||
|     |> Enum.map_join(". ", fn {key, errors} -> | ||||
|       "#{key |> humanize()}: #{errors |> Enum.join(", ")}" | ||||
|     end) | ||||
|   end | ||||
|  | ||||
|   def changeset_errors(changeset, key) do | ||||
|     changeset |> changeset_error_map() |> Map.get(key) | ||||
|   end | ||||
|  | ||||
|   @doc """ | ||||
|   Displays all errors from a changeset in a key value map | ||||
|   """ | ||||
|   @spec changeset_error_map(Changeset.t()) :: %{atom() => [String.t()]} | ||||
|   def changeset_error_map(changeset) do | ||||
|     changeset | ||||
|     |> Changeset.traverse_errors(fn error -> error |> translate_error() end) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -1,16 +1,16 @@ | ||||
| defmodule LokalWeb.ErrorView do | ||||
|   use LokalWeb, :view | ||||
|   import LokalWeb.Components.Topbar | ||||
|   alias LokalWeb.{Endpoint, PageLive} | ||||
|  | ||||
|   # If you want to customize a particular status code | ||||
|   # for a certain format, you may uncomment below. | ||||
|   # def render("500.html", _assigns) do | ||||
|   #   "Internal Server Error" | ||||
|   # end | ||||
|   def template_not_found(error_path, _assigns) do | ||||
|     error_string = | ||||
|       case error_path do | ||||
|         "404.html" -> dgettext("errors", "Not found") | ||||
|         "401.html" -> dgettext("errors", "Unauthorized") | ||||
|         _ -> dgettext("errors", "Internal Server Error") | ||||
|       end | ||||
|  | ||||
|   # By default, Phoenix returns the status message from | ||||
|   # the template name. For example, "404.html" becomes | ||||
|   # "Not Found". | ||||
|   def template_not_found(template, _assigns) do | ||||
|     Phoenix.Controller.status_message_from_template(template) | ||||
|     render("error.html", %{error_string: error_string}) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| defmodule LokalWeb.UserConfirmationView do | ||||
|   use LokalWeb, :view | ||||
|   alias Lokal.Accounts | ||||
| end | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| defmodule LokalWeb.UserResetPasswordView do | ||||
|   use LokalWeb, :view | ||||
|   alias Lokal.Accounts | ||||
| end | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| defmodule LokalWeb.UserSessionView do | ||||
|   use LokalWeb, :view | ||||
|   alias Lokal.Accounts | ||||
| end | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| defmodule LokalWeb.ViewHelpers do | ||||
|   @moduledoc """ | ||||
|   Contains common helpers that can be used in liveviews and regular views. These | ||||
|   are automatically imported into any Phoenix View using `use LokalWeb, :view` | ||||
|   are automatically imported into any Phoenix View using `use LokalWeb, | ||||
|   :view` | ||||
|   """ | ||||
|  | ||||
|   import Phoenix.LiveView.Helpers | ||||
| @@ -10,24 +11,42 @@ defmodule LokalWeb.ViewHelpers do | ||||
|   Returns a <time> element that renders the naivedatetime in the user's local | ||||
|   timezone with Alpine.js | ||||
|   """ | ||||
|   @spec display_datetime(NaiveDateTime.t()) :: Phoenix.LiveView.Rendered.t() | ||||
|   @spec display_datetime(NaiveDateTime.t() | nil) :: Phoenix.LiveView.Rendered.t() | ||||
|   def display_datetime(nil), do: "" | ||||
|  | ||||
|   def display_datetime(datetime) do | ||||
|     assigns = %{ | ||||
|       datetime: datetime |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_iso8601(:extended) | ||||
|     } | ||||
|  | ||||
|     ~H""" | ||||
|     <time | ||||
|       datetime={@datetime} | ||||
|       x-data={"{ | ||||
|     <time datetime={@datetime} x-data={"{ | ||||
|         date: | ||||
|           Intl.DateTimeFormat([], {dateStyle: 'short', timeStyle: 'long'}) | ||||
|             .format(new Date(\"#{@datetime}\")) | ||||
|       }"} | ||||
|       x-text="date" | ||||
|     > | ||||
|       }"} x-text="date"> | ||||
|       <%= @datetime %> | ||||
|     </time> | ||||
|     """ | ||||
|   end | ||||
|  | ||||
|   @doc """ | ||||
|   Returns a <date> element that renders the Date in the user's local | ||||
|   timezone with Alpine.js | ||||
|   """ | ||||
|   @spec display_date(Date.t() | nil) :: Phoenix.LiveView.Rendered.t() | ||||
|   def display_date(nil), do: "" | ||||
|  | ||||
|   def display_date(date) do | ||||
|     assigns = %{date: date |> Date.to_iso8601(:extended)} | ||||
|  | ||||
|     ~H""" | ||||
|     <time datetime={@date} x-data={"{ | ||||
|         date: | ||||
|           Intl.DateTimeFormat([], {timeZone: 'Etc/UTC', dateStyle: 'short'}).format(new Date(\"#{@date}\")) | ||||
|       }"} x-text="date"> | ||||
|       <%= @date %> | ||||
|     </time> | ||||
|     """ | ||||
|   end | ||||
| end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user