Skip to content

Commit

Permalink
Add flash kind for successful interactions
Browse files Browse the repository at this point in the history
Introduce a `:success` kind for flash messages in the generator
output and update relevant documentation.

Success messages that previously used `:info` now use `:success`,
ensuring they continue to appear in green with a "Success!" title.

In certain cases (e.g. password reset), `:info` is retained to avoid
disclosing too much information (such as the existence of an account).
These messages now appear in blue with a "Please note" title.

Rationale for those changes:

- Differentiate informational messages from successful actions

- Avoid confusion from informational messages styled in green, which
  can imply success

- Ensure `:info` is used for neutral messages, and use `:success` to
  clearly indicate completed actions
  • Loading branch information
rmoorman committed Nov 9, 2024
1 parent dad4527 commit 6f4838c
Show file tree
Hide file tree
Showing 22 changed files with 50 additions and 44 deletions.
6 changes: 3 additions & 3 deletions guides/contexts.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ defmodule HelloWeb.ProductController do
case Catalog.create_product(product_params) do
{:ok, product} ->
conn
|> put_flash(:info, "Product created successfully.")
|> put_flash(:success, "Product created successfully.")
|> redirect(to: ~p"/products/#{product}")

{:error, %Ecto.Changeset{} = changeset} ->
Expand Down Expand Up @@ -777,7 +777,7 @@ defmodule HelloWeb.CartItemController do
case ShoppingCart.add_item_to_cart(conn.assigns.cart, product_id) do
{:ok, _item} ->
conn
|> put_flash(:info, "Item added to your cart")
|> put_flash(:success, "Item added to your cart")
|> redirect(to: ~p"/cart")

{:error, _changeset} ->
Expand Down Expand Up @@ -1209,7 +1209,7 @@ defmodule HelloWeb.OrderController do
case Orders.complete_order(conn.assigns.cart) do
{:ok, order} ->
conn
|> put_flash(:info, "Order created successfully.")
|> put_flash(:success, "Order created successfully.")
|> redirect(to: ~p"/orders/#{order}")

{:error, _reason} ->
Expand Down
4 changes: 2 additions & 2 deletions guides/testing/testing_controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def create(conn, %{"post" => post_params}) do
case Blog.create_post(post_params) do
{:ok, post} ->
conn
|> put_flash(:info, "Post created successfully.")
|> put_flash(:success, "Post created successfully.")
|> redirect(to: ~p"/posts/#{post}")

{:error, %Ecto.Changeset{} = changeset} ->
Expand Down Expand Up @@ -142,7 +142,7 @@ def delete(conn, %{"id" => id}) do
{:ok, _post} = Blog.delete_post(post)

conn
|> put_flash(:info, "Post deleted successfully.")
|> put_flash(:success, "Post deleted successfully.")
|> redirect(to: ~p"/posts")
end
```
Expand Down
9 changes: 6 additions & 3 deletions installer/templates/phx_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ defmodule <%= @web_namespace %>.CoreComponents do
attr :id, :string, doc: "the optional id of flash container"
attr :flash, :map, default: %{}, doc: "the map of flash messages to display"
attr :title, :string, default: nil
attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
attr :kind, :atom, values: [:info, :success, :error], doc: "used for styling and flash lookup"
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"

slot :inner_block, doc: "the optional inner block that renders the flash message"
Expand All @@ -46,13 +46,15 @@ defmodule <%= @web_namespace %>.CoreComponents do
role="alert"
class={[
"fixed top-2 right-2 mr-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1",
@kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
@kind == :info && "bg-blue-50 text-blue-800 ring-blue-500 fill-blue-900",
@kind == :success && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-emerald-900",
@kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
]}
{@rest}
>
<p :if={@title} class="flex items-center gap-1.5 text-sm font-semibold leading-6">
<.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" />
<.icon :if={@kind == :success} name="hero-information-circle-mini" class="h-4 w-4" />
<.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" />
<%%= @title %>
</p>
Expand All @@ -77,7 +79,8 @@ defmodule <%= @web_namespace %>.CoreComponents do
def flash_group(assigns) do
~H"""
<div id={@id}>
<.flash kind={:info} title=<%= maybe_heex_attr_gettext.("Success!", @gettext) %> flash={@flash} />
<.flash kind={:info} title=<%= maybe_heex_attr_gettext.("Please note", @gettext) %> flash={@flash} />
<.flash kind={:success} title=<%= maybe_heex_attr_gettext.("Success!", @gettext) %> flash={@flash} />
<.flash kind={:error} title=<%= maybe_heex_attr_gettext.("Error!", @gettext) %> flash={@flash} />
<.flash
id="client-error"
Expand Down
4 changes: 2 additions & 2 deletions installer/test/phx_new_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ defmodule Mix.Tasks.Phx.NewTest do
assert_file("phx_blog/lib/phx_blog_web/components/core_components.ex", fn file ->
assert file =~ "defmodule PhxBlogWeb.CoreComponents"
assert file =~ ~S|aria-label={gettext("close")}|
assert file =~ ~S|<.flash kind={:info} title={gettext("Success!")} flash={@flash} />|
assert file =~ ~S|<.flash kind={:success} title={gettext("Success!")} flash={@flash} />|
end)

assert_file("phx_blog/lib/phx_blog_web/components/layouts.ex", fn file ->
Expand Down Expand Up @@ -567,7 +567,7 @@ defmodule Mix.Tasks.Phx.NewTest do

assert_file("phx_blog/lib/phx_blog_web/components/core_components.ex", fn file ->
assert file =~ ~S|aria-label="close"|
assert file =~ ~S|<.flash kind={:info} title="Success!" flash={@flash} />|
assert file =~ ~S|<.flash kind={:success} title="Success!" flash={@flash} />|
end)
end)
end
Expand Down
2 changes: 1 addition & 1 deletion priv/templates/phx.gen.auth/confirmation_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
case <%= inspect context.alias %>.confirm_<%= schema.singular %>(token) do
{:ok, _} ->
conn
|> put_flash(:info, "<%= schema.human_singular %> confirmed successfully.")
|> put_flash(:success, "<%= schema.human_singular %> confirmed successfully.")
|> redirect(to: ~p"/")

:error ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
conn = post(conn, ~p"<%= schema.route_prefix %>/confirm/#{token}")
assert redirected_to(conn) == ~p"/"

assert Phoenix.Flash.get(conn.assigns.flash, :info) =~
assert Phoenix.Flash.get(conn.assigns.flash, :success) =~
"<%= schema.human_singular %> confirmed successfully"

assert <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= schema.singular %>.id).confirmed_at
Expand Down
2 changes: 1 addition & 1 deletion priv/templates/phx.gen.auth/confirmation_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
{:ok, _} ->
{:noreply,
socket
|> put_flash(:info, "<%= inspect schema.alias %> confirmed successfully.")
|> put_flash(:success, "<%= inspect schema.alias %> confirmed successfully.")
|> redirect(to: ~p"/")}

:error ->
Expand Down
2 changes: 1 addition & 1 deletion priv/templates/phx.gen.auth/confirmation_live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web

assert {:ok, conn} = result

assert Phoenix.Flash.get(conn.assigns.flash, :info) =~
assert Phoenix.Flash.get(conn.assigns.flash, :success) =~
"<%= inspect schema.alias %> confirmed successfully"

assert <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= schema.singular %>.id).confirmed_at
Expand Down
2 changes: 1 addition & 1 deletion priv/templates/phx.gen.auth/registration_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
)

conn
|> put_flash(:info, "<%= schema.human_singular %> created successfully.")
|> put_flash(:success, "<%= schema.human_singular %> created successfully.")
|> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>)

{:error, %Ecto.Changeset{} = changeset} ->
Expand Down
2 changes: 1 addition & 1 deletion priv/templates/phx.gen.auth/reset_password_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
case <%= inspect context.alias %>.reset_<%= schema.singular %>_password(conn.assigns.<%= schema.singular %>, <%= schema.singular %>_params) do
{:ok, _} ->
conn
|> put_flash(:info, "Password reset successfully.")
|> put_flash(:success, "Password reset successfully.")
|> redirect(to: ~p"<%= schema.route_prefix %>/log_in")

{:error, changeset} ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
assert redirected_to(conn) == ~p"<%= schema.route_prefix %>/log_in"
refute get_session(conn, :<%= schema.singular %>_token)

assert Phoenix.Flash.get(conn.assigns.flash, :info) =~
assert Phoenix.Flash.get(conn.assigns.flash, :success) =~
"Password reset successfully"

assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(<%= schema.singular %>.email, "new valid password")
Expand Down
2 changes: 1 addition & 1 deletion priv/templates/phx.gen.auth/reset_password_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
{:ok, _} ->
{:noreply,
socket
|> put_flash(:info, "Password reset successfully.")
|> put_flash(:success, "Password reset successfully.")
|> redirect(to: ~p"<%= schema.route_prefix %>/log_in")}

{:error, changeset} ->
Expand Down
2 changes: 1 addition & 1 deletion priv/templates/phx.gen.auth/reset_password_live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
|> follow_redirect(conn, ~p"<%= schema.route_prefix %>/log_in")

refute get_session(conn, :<%= schema.singular %>_token)
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Password reset successfully"
assert Phoenix.Flash.get(conn.assigns.flash, :success) =~ "Password reset successfully"
assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(<%= schema.singular %>.email, "new valid password")
end

Expand Down
12 changes: 6 additions & 6 deletions priv/templates/phx.gen.auth/session_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
alias <%= inspect auth_module %><%= if live? do %>

def create(conn, %{"_action" => "registered"} = params) do
create(conn, params, "Account created successfully!")
create(conn, params, :success, "Account created successfully!")
end

def create(conn, %{"_action" => "password_updated"} = params) do
conn
|> put_session(:<%= schema.singular %>_return_to, ~p"<%= schema.route_prefix %>/settings")
|> create(params, "Password updated successfully!")
|> create(params, :success, "Password updated successfully!")
end

def create(conn, params) do
create(conn, params, "Welcome back!")
create(conn, params, :info, "Welcome back!")
end

defp create(conn, %{"<%= schema.singular %>" => <%= schema.singular %>_params}, info) do
defp create(conn, %{"<%= schema.singular %>" => <%= schema.singular %>_params}, kind, message) do
%{"email" => email, "password" => password} = <%= schema.singular %>_params

if <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(email, password) do
conn
|> put_flash(:info, info)
|> put_flash(kind, message)
|> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>, <%= schema.singular %>_params)
else
# In order to prevent user enumeration attacks, don't disclose whether the email is registered.
Expand Down Expand Up @@ -53,7 +53,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web

def delete(conn, _params) do
conn
|> put_flash(:info, "Logged out successfully.")
|> put_flash(:success, "Logged out successfully.")
|> <%= inspect schema.alias %>Auth.log_out_<%= schema.singular %>()
end
end
8 changes: 4 additions & 4 deletions priv/templates/phx.gen.auth/session_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
})

assert redirected_to(conn) == ~p"/"
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Account created successfully"
assert Phoenix.Flash.get(conn.assigns.flash, :success) =~ "Account created successfully"
end

test "login following password update", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
Expand All @@ -96,7 +96,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
})

assert redirected_to(conn) == ~p"<%= schema.route_prefix %>/settings"
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Password updated successfully"
assert Phoenix.Flash.get(conn.assigns.flash, :success) =~ "Password updated successfully"
end

test "redirects to login page with invalid credentials", %{conn: conn} do
Expand Down Expand Up @@ -126,14 +126,14 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
conn = conn |> log_in_<%= schema.singular %>(<%= schema.singular %>) |> delete(~p"<%= schema.route_prefix %>/log_out")
assert redirected_to(conn) == ~p"/"
refute get_session(conn, :<%= schema.singular %>_token)
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully"
assert Phoenix.Flash.get(conn.assigns.flash, :success) =~ "Logged out successfully"
end

test "succeeds even if the <%= schema.singular %> is not logged in", %{conn: conn} do
conn = delete(conn, ~p"<%= schema.route_prefix %>/log_out")
assert redirected_to(conn) == ~p"/"
refute get_session(conn, :<%= schema.singular %>_token)
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully"
assert Phoenix.Flash.get(conn.assigns.flash, :success) =~ "Logged out successfully"
end
end
end
4 changes: 2 additions & 2 deletions priv/templates/phx.gen.auth/settings_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
case <%= inspect context.alias %>.update_<%= schema.singular %>_password(<%= schema.singular %>, password, <%= schema.singular %>_params) do
{:ok, <%= schema.singular %>} ->
conn
|> put_flash(:info, "Password updated successfully.")
|> put_flash(:success, "Password updated successfully.")
|> put_session(:<%= schema.singular %>_return_to, ~p"<%= schema.route_prefix %>/settings")
|> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>)

Expand All @@ -54,7 +54,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
case <%= inspect context.alias %>.update_<%= schema.singular %>_email(conn.assigns.current_<%= schema.singular %>, token) do
:ok ->
conn
|> put_flash(:info, "Email changed successfully.")
|> put_flash(:success, "Email changed successfully.")
|> redirect(to: ~p"<%= schema.route_prefix %>/settings")

:error ->
Expand Down
4 changes: 2 additions & 2 deletions priv/templates/phx.gen.auth/settings_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web

assert get_session(new_password_conn, :<%= schema.singular %>_token) != get_session(conn, :<%= schema.singular %>_token)

assert Phoenix.Flash.get(new_password_conn.assigns.flash, :info) =~
assert Phoenix.Flash.get(new_password_conn.assigns.flash, :success) =~
"Password updated successfully"

assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(<%= schema.singular %>.email, "new valid password")
Expand Down Expand Up @@ -112,7 +112,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
conn = get(conn, ~p"<%= schema.route_prefix %>/settings/confirm_email/#{token}")
assert redirected_to(conn) == ~p"<%= schema.route_prefix %>/settings"

assert Phoenix.Flash.get(conn.assigns.flash, :info) =~
assert Phoenix.Flash.get(conn.assigns.flash, :success) =~
"Email changed successfully"

refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(<%= schema.singular %>.email)
Expand Down
2 changes: 1 addition & 1 deletion priv/templates/phx.gen.auth/settings_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
socket =
case <%= inspect context.alias %>.update_<%= schema.singular %>_email(socket.assigns.current_<%= schema.singular %>, token) do
:ok ->
put_flash(socket, :info, "Email changed successfully.")
put_flash(socket, :success, "Email changed successfully.")

:error ->
put_flash(socket, :error, "Email change link is invalid or it has expired.")
Expand Down
4 changes: 2 additions & 2 deletions priv/templates/phx.gen.auth/settings_live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web

assert get_session(new_password_conn, :<%= schema.singular %>_token) != get_session(conn, :<%= schema.singular %>_token)

assert Phoenix.Flash.get(new_password_conn.assigns.flash, :info) =~
assert Phoenix.Flash.get(new_password_conn.assigns.flash, :success) =~
"Password updated successfully"

assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(<%= schema.singular %>.email, new_password)
Expand Down Expand Up @@ -176,7 +176,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web

assert {:live_redirect, %{to: path, flash: flash}} = redirect
assert path == ~p"<%= schema.route_prefix %>/settings"
assert %{"info" => message} = flash
assert %{"success" => message} = flash
assert message == "Email changed successfully."
refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(<%= schema.singular %>.email)
assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(email)
Expand Down
6 changes: 3 additions & 3 deletions priv/templates/phx.gen.html/controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
case <%= inspect context.alias %>.create_<%= schema.singular %>(<%= schema.singular %>_params) do
{:ok, <%= schema.singular %>} ->
conn
|> put_flash(:info, "<%= schema.human_singular %> created successfully.")
|> put_flash(:success, "<%= schema.human_singular %> created successfully.")
|> redirect(to: ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}")
{:error, %Ecto.Changeset{} = changeset} ->
Expand All @@ -43,7 +43,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
case <%= inspect context.alias %>.update_<%= schema.singular %>(<%= schema.singular %>, <%= schema.singular %>_params) do
{:ok, <%= schema.singular %>} ->
conn
|> put_flash(:info, "<%= schema.human_singular %> updated successfully.")
|> put_flash(:success, "<%= schema.human_singular %> updated successfully.")
|> redirect(to: ~p"<%= schema.route_prefix %>/#{<%= schema.singular %>}")

{:error, %Ecto.Changeset{} = changeset} ->
Expand All @@ -56,7 +56,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
{:ok, _<%= schema.singular %>} = <%= inspect context.alias %>.delete_<%= schema.singular %>(<%= schema.singular %>)

conn
|> put_flash(:info, "<%= schema.human_singular %> deleted successfully.")
|> put_flash(:success, "<%= schema.human_singular %> deleted successfully.")
|> redirect(to: ~p"<%= schema.route_prefix %>")
end
end
Loading

0 comments on commit 6f4838c

Please sign in to comment.