Skip to content

Commit 4319128

Browse files
committed
Update auth pages visually
- Create new auth layout context that looks visually cleaner, and hides the header and footer - Transform all auth pages to use the new context. This includes: - Login page - Signup page - Password reset page - Password reset request page - Password reset request success page - Password reset error page - Activate account page - Verify 2FA page - Verify 2FA recovery code page - SSO login page - SSO provision notice page - SSO provision issue page - Invitation expired page - Tighten copy across auth pages to be more consistent and concise - Remove password conformation field from signup page - Extract password field to a new component with visibility toggle - Extract Plausible logo to a new component
1 parent c3e9c02 commit 4319128

39 files changed

Lines changed: 941 additions & 841 deletions

e2e/tests/fixtures.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ export async function register({
124124
export async function login({ page, user }: { page: Page; user: User }) {
125125
await page.goto('/login', { waitUntil: 'commit' })
126126

127-
await expect(page.getByRole('button', { name: 'Log in' })).toBeVisible()
127+
await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible()
128128

129129
await page.getByLabel('Email').fill(user.email)
130130
await page.getByLabel('Password').fill(user.password)
131-
await page.getByRole('button', { name: 'Log in' }).click()
131+
await page.getByRole('button', { name: 'Sign in' }).click()
132132

133133
await expect(page.getByRole('button', { name: user.name })).toBeVisible()
134134
}

extra/lib/plausible_web/controllers/sso_controller.ex

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ defmodule PlausibleWeb.SSOController do
2424
redirect(conn, to: Routes.auth_path(conn, :login_form, return_to: params["return_to"]))
2525

2626
_ ->
27-
render(conn, "login_form.html", autosubmit: params["autosubmit"] != nil)
27+
render_auth_page(conn, "login_form.html",
28+
autosubmit: params["autosubmit"] != nil,
29+
heading: "Sign in with SSO"
30+
)
2831
end
2932
end
3033

@@ -44,7 +47,10 @@ defmodule PlausibleWeb.SSOController do
4447
else
4548
{:error, :not_found} ->
4649
conn
47-
|> put_flash(:login_error, "Wrong email.")
50+
|> put_flash(
51+
:login_error,
52+
"We couldn't find a Single Sign-On account for that email."
53+
)
4854
|> redirect(to: Routes.sso_path(conn, :login_form))
4955

5056
{:error, {:rate_limit, _}} ->
@@ -59,7 +65,7 @@ defmodule PlausibleWeb.SSOController do
5965
end
6066

6167
def provision_notice(conn, _params) do
62-
render(conn, "provision_notice.html")
68+
render_auth_page(conn, "provision_notice.html", heading: "Single Sign-On required")
6369
end
6470

6571
def provision_issue(conn, params) do
@@ -73,7 +79,10 @@ defmodule PlausibleWeb.SSOController do
7379
_ -> :unknown
7480
end
7581

76-
render(conn, "provision_issue.html", issue: issue)
82+
render_auth_page(conn, "provision_issue.html",
83+
heading: "Single Sign-On required",
84+
issue: issue
85+
)
7786
end
7887

7988
def saml_signin(conn, params) do

extra/lib/plausible_web/sso/fake_saml_adapter.ex

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ defmodule PlausibleWeb.SSO.FakeSAMLAdapter do
5656

5757
{:error, :not_found} ->
5858
conn
59-
|> Phoenix.Controller.put_flash(:login_error, "Wrong email.")
59+
|> Phoenix.Controller.put_flash(
60+
:login_error,
61+
"We couldn't find a Single Sign-On account for that email."
62+
)
6063
|> Phoenix.Controller.redirect(
6164
to: Routes.sso_path(conn, :login_form, return_to: params["return_to"])
6265
)

extra/lib/plausible_web/sso/real_saml_adapter.ex

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ defmodule PlausibleWeb.SSO.RealSAMLAdapter do
5050

5151
{:error, :not_found} ->
5252
conn
53-
|> Phoenix.Controller.put_flash(:login_error, "Wrong email.")
53+
|> Phoenix.Controller.put_flash(
54+
:login_error,
55+
"We couldn't find a Single Sign-On account for that email."
56+
)
5457
|> Phoenix.Controller.redirect(
5558
to: Routes.sso_path(conn, :login_form, return_to: return_to)
5659
)
@@ -108,7 +111,11 @@ defmodule PlausibleWeb.SSO.RealSAMLAdapter do
108111
PlausibleWeb.UserAuth.log_in_user(conn, identity, cookie.return_to)
109112
else
110113
{:error, :not_found} ->
111-
login_error(conn, cookie, "Wrong email")
114+
login_error(
115+
conn,
116+
cookie,
117+
"We couldn't find a Single Sign-On account for that email."
118+
)
112119

113120
{:error, reason} ->
114121
with {:ok, integration} <- SSO.get_integration(integration_id) do
Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,54 @@
1-
<.focus_box>
2-
<:title>
3-
{Phoenix.Flash.get(@flash, :login_title) || "Enter your Single Sign-On email"}
4-
</:title>
5-
<:subtitle>
6-
<%= if Phoenix.Flash.get(@flash, :login_instructions) do %>
7-
<p class="text-gray-500 mt-1 mb-2">
8-
{Phoenix.Flash.get(@flash, :login_instructions)}
9-
</p>
10-
<% end %>
11-
</:subtitle>
1+
<div class="w-full max-w-md mx-auto mt-10 pb-16 px-4">
122
<script :if={@autosubmit}>
133
document.addEventListener("DOMContentLoaded", function () {
144
document.getElementById("sso-login-form").submit();
155
});
166
</script>
17-
<.form :let={f} id="sso-login-form" for={@conn} action={Routes.sso_path(@conn, :login)}>
18-
<div class="my-4 mt-8">
19-
<.input
20-
type="email"
21-
autocomplete="username"
22-
placeholder="user@example.com"
23-
field={f[:email]}
24-
/>
25-
</div>
267

27-
<%= if login_error = Phoenix.Flash.get(@flash, :login_error) do %>
28-
<div class="text-red-500 mt-4">{login_error}</div>
29-
<% end %>
8+
<.form
9+
:let={f}
10+
id="sso-login-form"
11+
for={@conn}
12+
action={Routes.sso_path(@conn, :login)}
13+
class="flex flex-col gap-y-6"
14+
>
15+
<div class="flex flex-col gap-y-2">
16+
<label for={f[:email].id} class="text-sm font-semibold text-gray-800 dark:text-gray-200">
17+
Email
18+
</label>
19+
<div>
20+
<.input
21+
type="email"
22+
autocomplete="username"
23+
placeholder="example@email.com"
24+
field={f[:email]}
25+
mt?={false}
26+
autofocus="autofocus"
27+
/>
28+
</div>
29+
<p
30+
:if={login_error = Phoenix.Flash.get(@flash, :login_error)}
31+
class="text-xs text-red-500"
32+
>
33+
{login_error}
34+
</p>
35+
</div>
3036

31-
<.input type="hidden" field={f[:return_to]} />
37+
<input type="hidden" name="return_to" value={@conn.params["return_to"]} />
3238

33-
<.button class="w-full" type="submit">Sign In</.button>
34-
</.form>
39+
<div class="flex flex-col gap-y-4">
40+
<.button class="w-full" type="submit" mt?={false}>Sign in</.button>
3541

36-
<:footer>
37-
<.focus_list>
38-
<:item>
39-
Have a standard account?
42+
<p class="text-sm text-center text-gray-500 dark:text-gray-400">
4043
<.styled_link href={
4144
Routes.auth_path(@conn, :login_form,
4245
return_to: @conn.params["return_to"],
4346
prefer: "manual"
4447
)
4548
}>
46-
Log in here
49+
Use password instead
4750
</.styled_link>
48-
instead.
49-
</:item>
50-
</.focus_list>
51-
</:footer>
52-
</.focus_box>
51+
</p>
52+
</div>
53+
</.form>
54+
</div>
Lines changed: 31 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,36 @@
1-
<.focus_box>
2-
<:title>
3-
Single Sign-On enforcement
4-
</:title>
5-
<:subtitle>
6-
The owner of <span :if={is_nil(@conn.assigns[:current_team])}>the team</span>
7-
<span :if={@conn.assigns[:current_team]}>"{@conn.assigns[:current_team].name}"</span>
8-
has turned off regular email and password logins.
9-
To keep things secure and simple, you can only sign in using your organization's
10-
Single Sign-On (SSO) system.
11-
</:subtitle>
12-
13-
<p :if={@issue == :multiple_memberships} class="text-sm">
14-
To access this team, you must first leave all other teams.
15-
</p>
16-
17-
<p :if={@issue == :multiple_memberships_noforce} class="text-sm">
18-
To log in as an SSO user, you must first leave all other teams.
19-
</p>
20-
21-
<div :if={@issue == :active_personal_team} class="text-sm flex flex-col space-y-2">
22-
<p>
23-
To access this team, you must either remove or transfer all sites you own under "My Personal Sites".
24-
</p>
25-
26-
<p>
27-
You also have to cancel subscription for "My Personal Sites" if there is an active one.
28-
</p>
29-
</div>
30-
31-
<div :if={@issue == :active_personal_team_noforce} class="text-sm flex flex-col space-y-2">
32-
<p>
33-
To log in as an SSO user, you must either remove or transfer all sites you own under "My Personal Sites".
34-
</p>
35-
36-
<p>
37-
You also have to cancel subscription on "My Personal Sites" if there is an active one.
38-
</p>
39-
</div>
40-
41-
<p :if={@issue == :not_a_member} class="text-sm">
42-
To access this team, you must join as a team member first.
1+
<div class="w-full max-w-md mx-auto mt-6 pb-16 px-4 flex flex-col items-center gap-y-4 text-center text-base text-gray-500 dark:text-gray-400 text-pretty">
2+
<p>
3+
The owner of
4+
<span :if={is_nil(@conn.assigns[:current_team])}>the team</span><span
5+
:if={@conn.assigns[:current_team]}
6+
class="font-semibold"
7+
>"{@conn.assigns[:current_team].name}"</span>
8+
has turned off email and password logins, so you can only sign in with your organization's Single Sign-On.
9+
<span :if={@issue == :not_a_member}>
10+
To access this team, you must be added as a team member first.
11+
</span>
12+
<span :if={@issue == :multiple_memberships}>
13+
To access this team, you must leave all other teams first.
14+
</span>
15+
<span :if={@issue == :multiple_memberships_noforce}>
16+
To sign in with SSO, you must leave all other teams first.
17+
</span>
18+
<span :if={@issue == :active_personal_team}>
19+
To access this team, remove or transfer all sites under
20+
<span class="font-semibold">"My Personal Sites"</span>
21+
and cancel its subscription if active.
22+
</span>
23+
<span :if={@issue == :active_personal_team_noforce}>
24+
To sign in with SSO, remove or transfer all sites under
25+
<span class="font-semibold">"My Personal Sites"</span>
26+
and cancel its subscription if active.
27+
</span>
4328
</p>
4429

45-
<p :if={!@conn.assigns[:current_user]} class="text-sm mt-4">
30+
<p :if={!@conn.assigns[:current_user]}>
4631
<.styled_link href={Routes.auth_path(@conn, :login_form, prefer: "manual")}>
47-
Log in
32+
Sign in with email and password
4833
</.styled_link>
49-
with your email and password to resolve the issue.
34+
to resolve the issue.
5035
</p>
51-
</.focus_box>
36+
</div>
Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
<.focus_box>
2-
<:title>
3-
Single Sign-On enforcement
4-
</:title>
5-
<:subtitle>
6-
The owner of <span :if={is_nil(@conn.assigns[:current_team])}>the team</span>
7-
<span :if={@conn.assigns[:current_team]}>"{@conn.assigns[:current_team].name}"</span>
8-
has turned off regular email and password logins.
9-
To keep things secure and simple, you can only sign in using your organization's
10-
Single Sign-On (SSO) system.
11-
</:subtitle>
12-
13-
<p class="text-sm">
14-
To access this team, you must first <.styled_link href="/logout">log out</.styled_link>
15-
and log in as SSO user.
1+
<div class="w-full max-w-md mx-auto mt-6 pb-16 px-4 flex flex-col items-center gap-y-4 text-center text-base text-gray-500 dark:text-gray-400 text-pretty">
2+
<p>
3+
The owner of
4+
<span :if={is_nil(@conn.assigns[:current_team])}>the team</span><span
5+
:if={@conn.assigns[:current_team]}
6+
class="font-semibold"
7+
>"{@conn.assigns[:current_team].name}"</span>
8+
has turned off email and password logins, so you can only sign in with your organization's Single Sign-On.
169
</p>
17-
</.focus_box>
10+
<p>
11+
To access this team,
12+
<.styled_link href={Routes.auth_path(@conn, :logout)}>sign out</.styled_link>
13+
and sign in with SSO.
14+
</p>
15+
</div>

lib/plausible/auth/user.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,10 @@ defmodule Plausible.Auth.User do
7979
def new(attrs \\ %{}) do
8080
%Plausible.Auth.User{}
8181
|> cast(attrs, @required)
82-
|> validate_required(@required)
82+
|> validate_required(:name, message: "Please enter your name")
83+
|> validate_required(:email, message: "Please enter your email")
84+
|> validate_required(:password, message: "Please enter a password")
8385
|> validate_password_length()
84-
|> validate_confirmation(:password, required: true)
8586
|> validate_password_strength()
8687
|> hash_password()
8788
|> set_email_verification_status()

lib/plausible_web/components/layout.ex

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,28 @@ defmodule PlausibleWeb.Components.Layout do
33

44
use Phoenix.Component
55

6+
attr :class, :string, default: "w-24 sm:w-30"
7+
8+
def logo(assigns) do
9+
~H"""
10+
<img
11+
src={logo_url("logo_dark.svg")}
12+
class={[@class, "hidden dark:inline"]}
13+
alt="Plausible logo"
14+
loading="lazy"
15+
/>
16+
<img
17+
src={logo_url("logo_light.svg")}
18+
class={[@class, "inline dark:hidden"]}
19+
alt="Plausible logo"
20+
loading="lazy"
21+
/>
22+
"""
23+
end
24+
25+
defp logo_url(filename),
26+
do: PlausibleWeb.Router.Helpers.static_path(PlausibleWeb.Endpoint, logo_path(filename))
27+
628
def favicon(assigns) do
729
~H"""
830
<link

0 commit comments

Comments
 (0)