From 7217ea97fc968efee434dfd9917aa84c63f4b63e Mon Sep 17 00:00:00 2001 From: Vinicius Brasil Date: Thu, 5 Nov 2026 17:48:37 -0300 Subject: [PATCH 1/4] Match team switcher font weight with other menu links Non-selected teams in the team switcher used font-medium, while the other links in the account menu use the default weight. This commit removes font-medium so they match. --- lib/plausible_web/views/layout_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plausible_web/views/layout_view.ex b/lib/plausible_web/views/layout_view.ex index 210e886c4fe0..1c47144ff66d 100644 --- a/lib/plausible_web/views/layout_view.ex +++ b/lib/plausible_web/views/layout_view.ex @@ -220,7 +220,7 @@ defmodule PlausibleWeb.LayoutView do href={~p"/sites?#{[__team: team.identifier]}"} >

{Teams.name(team)} From 323bf9a325d2d0f3522eab9f75244ec30dcd0d79 Mon Sep 17 00:00:00 2001 From: Vinicius Brasil Date: Thu, 5 Nov 2026 17:49:16 -0300 Subject: [PATCH 2/4] Always show My Personal Sites in the team switcher The team switcher was hidden when the user had no other teams, so My Personal Sites was not visible in the account menu. This commit removes that guard, so the switcher always renders. --- lib/plausible_web/components/layouts.ex | 3 + .../components/layouts/header.html.heex | 1 + lib/plausible_web/views/layout_view.ex | 103 +++++++++--------- .../controllers/settings_controller_test.exs | 18 ++- test/plausible_web/live/sites_test.exs | 11 ++ test/plausible_web/live/team_setup_test.exs | 6 + 6 files changed, 84 insertions(+), 58 deletions(-) diff --git a/lib/plausible_web/components/layouts.ex b/lib/plausible_web/components/layouts.ex index 5a2604508ec0..1d916e5e977a 100644 --- a/lib/plausible_web/components/layouts.ex +++ b/lib/plausible_web/components/layouts.ex @@ -15,6 +15,7 @@ defmodule PlausibleWeb.Layouts do attr :footer?, :boolean, default: true attr :global_notices?, :boolean, default: true attr :trial_badge?, :boolean, default: true + attr :team_switcher?, :boolean, default: true attr :embedded?, :boolean, default: false attr :load_dashboard_js?, :boolean, default: false attr :flash, :map, default: %{} @@ -38,6 +39,7 @@ defmodule PlausibleWeb.Layouts do current_team={@current_team} current_team_role={@current_team_role} trial_badge?={@trial_badge?} + team_switcher?={@team_switcher?} teams={@teams} my_team={@my_team} /> @@ -152,6 +154,7 @@ defmodule PlausibleWeb.Layouts do footer?={false} global_notices?={false} trial_badge?={false} + team_switcher?={false} current_user={@current_user} flash={@flash} > diff --git a/lib/plausible_web/components/layouts/header.html.heex b/lib/plausible_web/components/layouts/header.html.heex index c463509794f2..0b803bfa9411 100644 --- a/lib/plausible_web/components/layouts/header.html.heex +++ b/lib/plausible_web/components/layouts/header.html.heex @@ -63,6 +63,7 @@

assign(:pinned_team, List.first(pinned_team)) - |> assign(:other_teams, other_teams) + assigns = + assigns + |> assign(:pinned_team, List.first(pinned_team)) + |> assign(:other_teams, other_teams) - ~H""" - <.dropdown_item> -
Teams
- + ~H""" + <.dropdown_item> +
Teams
+ + <.dropdown_item + :if={@pinned_team} + href={~p"/sites?#{[__team: @pinned_team.identifier]}"} + > +
+

+ {Teams.name(@pinned_team)} +

+ +
+ +
<.dropdown_item - :if={@pinned_team} - href={~p"/sites?#{[__team: @pinned_team.identifier]}"} + :for={team <- @other_teams} + href={~p"/sites?#{[__team: team.identifier]}"} > -
-

- {Teams.name(@pinned_team)} -

- -
- -
- <.dropdown_item - :for={team <- @other_teams} - href={~p"/sites?#{[__team: team.identifier]}"} +

-

- {Teams.name(team)} -

- -
- """ - else - ~H"" - end + {Teams.name(team)} +

+ +
+ """ end def trial_notification(team) do diff --git a/test/plausible_web/controllers/settings_controller_test.exs b/test/plausible_web/controllers/settings_controller_test.exs index a2583dafae82..55b2a63c1abf 100644 --- a/test/plausible_web/controllers/settings_controller_test.exs +++ b/test/plausible_web/controllers/settings_controller_test.exs @@ -1561,7 +1561,7 @@ defmodule PlausibleWeb.SettingsControllerTest do %{conn: conn} do conn = get(conn, ~p"/settings/preferences") html = html_response(conn, 200) - refute html =~ "Team" + refute html =~ "Team settings" expected_account_menu = if(ee?(), @@ -1700,11 +1700,21 @@ defmodule PlausibleWeb.SettingsControllerTest do end test "does not render team settings, when team not set up", %{conn: conn, user: user} do - {:ok, team} = Plausible.Teams.get_or_create(user) + {:ok, _team} = Plausible.Teams.get_or_create(user) conn = get(conn, ~p"/settings/preferences") html = html_response(conn, 200) - refute html =~ ~r/Team.*#{Regex.escape(team.name)}/s - refute html =~ team.name + refute html =~ "Team settings" + + expected_account_menu = + if(ee?(), + do: [:preferences, :security, :subscription, :api_keys, :danger_zone], + else: [:preferences, :security, :api_keys, :danger_zone] + ) + + html + |> LazyHTML.from_document() + |> assert_sidebar_menu(expected_account_menu) + |> assert_mobile_menu(expected_account_menu) end test "GET /settings/team/general", %{conn: conn, user: user} do diff --git a/test/plausible_web/live/sites_test.exs b/test/plausible_web/live/sites_test.exs index 259c5877bbf0..e4d48f4c8f70 100644 --- a/test/plausible_web/live/sites_test.exs +++ b/test/plausible_web/live/sites_test.exs @@ -40,6 +40,17 @@ defmodule PlausibleWeb.Live.SitesTest do ) end + test "lists personal sites in team switcher when the user has no other teams", %{ + conn: conn, + user: user + } do + new_site(owner: user) + + {:ok, _lv, html} = live(conn, "/sites") + + assert text_of_element(html, ~s|a[href^="/sites?__team="]|) == "My personal sites" + end + test "renders settings link when current team is set", %{user: user, conn: conn} do {:ok, _lv, html} = live(conn, "/sites") diff --git a/test/plausible_web/live/team_setup_test.exs b/test/plausible_web/live/team_setup_test.exs index 989832899721..c103b3af7c5c 100644 --- a/test/plausible_web/live/team_setup_test.exs +++ b/test/plausible_web/live/team_setup_test.exs @@ -44,6 +44,12 @@ defmodule PlausibleWeb.Live.TeamSetupTest do "Set up team" end + test "does not render the team switcher", %{conn: conn, site: site} do + {:ok, _lv, html} = live(conn, onboarding_url(site.domain)) + + refute html =~ "Create new team" + end + test "suggests a team name based on the site domain", %{conn: conn, user: user} do site = new_site(owner: user, domain: "shop.my-company.co.uk") From 45212edd696ee9eb5d213decfe7348fbcb9b62d5 Mon Sep 17 00:00:00 2001 From: Vinicius Brasil Date: Thu, 5 Nov 2026 17:49:40 -0300 Subject: [PATCH 3/4] Sort teams in the team switcher The team order in the switcher depended on membership order, and My Personal Sites was always last. This commit lists the current selection first, My Personal Sites second, then the other teams sorted by name. --- lib/plausible_web/views/layout_view.ex | 35 +++++++------------------- test/plausible_web/live/sites_test.exs | 21 ++++++++++++++++ 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/lib/plausible_web/views/layout_view.ex b/lib/plausible_web/views/layout_view.ex index b9e0bd28db20..b77916d09768 100644 --- a/lib/plausible_web/views/layout_view.ex +++ b/lib/plausible_web/views/layout_view.ex @@ -169,45 +169,28 @@ defmodule PlausibleWeb.LayoutView do attr :current_team, :any, default: nil def team_switcher(assigns) do - teams = assigns.teams - current_team = assigns[:current_team] - my_team = assigns[:my_team] - current_included? = current_team && Enum.any?(teams, &(&1.id == current_team.id)) - current_is_my? = current_team && my_team && current_team.id == my_team.id + personal_team = + assigns.my_team || %Teams.Team{identifier: "none", name: Teams.default_name()} - teams = - if current_team && !current_included? && !current_is_my? do - [current_team | teams] - else - teams - end - - teams = - if my_team do - teams ++ [my_team] - else - teams ++ [%Teams.Team{identifier: "none", name: Teams.default_name()}] - end + current_team = assigns.current_team || personal_team - selected_id = current_team && current_team.id - {pinned_team, other_teams} = Enum.split_with(teams, &(&1.id == selected_id)) + other_teams = + [personal_team | Enum.sort_by(assigns.teams, &String.downcase(&1.name))] + |> Enum.reject(&(&1.id == current_team.id)) assigns = assigns - |> assign(:pinned_team, List.first(pinned_team)) + |> assign(:current_team, current_team) |> assign(:other_teams, other_teams) ~H""" <.dropdown_item>
Teams
- <.dropdown_item - :if={@pinned_team} - href={~p"/sites?#{[__team: @pinned_team.identifier]}"} - > + <.dropdown_item href={~p"/sites?#{[__team: @current_team.identifier]}"}>

- {Teams.name(@pinned_team)} + {Teams.name(@current_team)}

diff --git a/test/plausible_web/live/sites_test.exs b/test/plausible_web/live/sites_test.exs index e4d48f4c8f70..90432976a82f 100644 --- a/test/plausible_web/live/sites_test.exs +++ b/test/plausible_web/live/sites_test.exs @@ -51,6 +51,27 @@ defmodule PlausibleWeb.Live.SitesTest do assert text_of_element(html, ~s|a[href^="/sites?__team="]|) == "My personal sites" end + test "lists current team first, personal sites second, then other teams by name", %{ + conn: conn, + user: user + } do + [zeta, beta, alpha] = + for name <- ["Zeta", "Beta", "Alpha"] do + team = insert(:team, name: name, identifier: Ecto.UUID.generate()) + add_member(team, user: user, role: :viewer) + team + end + + {:ok, _lv, html} = live(set_current_team(conn, zeta), "/sites") + + assert html |> find(~s|a[href^="/sites?__team="]|) |> LazyHTML.attribute("href") == [ + "/sites?__team=#{zeta.identifier}", + "/sites?__team=none", + "/sites?__team=#{alpha.identifier}", + "/sites?__team=#{beta.identifier}" + ] + end + test "renders settings link when current team is set", %{user: user, conn: conn} do {:ok, _lv, html} = live(conn, "/sites") From c5433a979711044a5e8fca5d93fe9a19527914fe Mon Sep 17 00:00:00 2001 From: Vinicius Brasil Date: Thu, 5 Nov 2026 17:50:00 -0300 Subject: [PATCH 4/4] Show "Create new team" for every team switcher selection The "Create a team" link showed only when My Personal Sites was the current selection. This commit moves it into the team switcher as "Create new team", so it shows for every selection. It always links to the personal team, because new teams are created from there. --- .../components/layouts/header.html.heex | 13 +----- lib/plausible_web/views/layout_view.ex | 11 +++++ .../controllers/settings_controller_test.exs | 42 +++++++++++++++---- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/lib/plausible_web/components/layouts/header.html.heex b/lib/plausible_web/components/layouts/header.html.heex index 0b803bfa9411..a3673f6ca57b 100644 --- a/lib/plausible_web/components/layouts/header.html.heex +++ b/lib/plausible_web/components/layouts/header.html.heex @@ -64,6 +64,7 @@ -
- <.dropdown_item - class="flex" - href={~p"/team/setup"} - > - - Create a team - - - <.dropdown_divider /> -
-
<.dropdown_item class="flex" diff --git a/lib/plausible_web/views/layout_view.ex b/lib/plausible_web/views/layout_view.ex index b77916d09768..5f79de807fe5 100644 --- a/lib/plausible_web/views/layout_view.ex +++ b/lib/plausible_web/views/layout_view.ex @@ -164,6 +164,7 @@ defmodule PlausibleWeb.LayoutView do end end + attr :current_user, :any, required: true attr :teams, :list, required: true attr :my_team, :any, default: nil attr :current_team, :any, default: nil @@ -181,6 +182,7 @@ defmodule PlausibleWeb.LayoutView do assigns = assigns |> assign(:current_team, current_team) + |> assign(:personal_team, personal_team) |> assign(:other_teams, other_teams) ~H""" @@ -208,6 +210,15 @@ defmodule PlausibleWeb.LayoutView do

+ <.dropdown_item + :if={Plausible.Users.type(@current_user) == :standard} + href={~p"/team/setup?#{[__team: @personal_team.identifier]}"} + > +
+ Create new team + +
+ """ end diff --git a/test/plausible_web/controllers/settings_controller_test.exs b/test/plausible_web/controllers/settings_controller_test.exs index 55b2a63c1abf..3d9a297c3bdf 100644 --- a/test/plausible_web/controllers/settings_controller_test.exs +++ b/test/plausible_web/controllers/settings_controller_test.exs @@ -2246,22 +2246,48 @@ defmodule PlausibleWeb.SettingsControllerTest do describe "account dropdown menu (_header.html)" do setup [:create_user, :log_in] - test "renders the 'Create a team' option", %{conn: conn, user: user} do + test "renders the 'Create new team' option", %{conn: conn, user: user} do subscribe_to_growth_plan(user) conn = get(conn, ~p"/settings/preferences") html = html_response(conn, 200) - assert text_of_element(html, ~s/[data-test="create-a-team-cta"]/) == "Create a team" + assert text_of_element(html, ~s/[data-test="create-a-team-cta"]/) == "Create new team" end - test "does not render the 'Create a team' option if a team is already set up", %{ - conn: conn, - user: user - } do + test "renders the 'Create new team' option for the personal team when a team is already set up", + %{conn: conn, user: user} do {:ok, team} = Plausible.Teams.get_or_create(user) Plausible.Teams.complete_setup(team) - conn = get(conn, ~p"/settings/preferences") + conn = get(set_current_team(conn, team), ~p"/settings/preferences") + html = html_response(conn, 200) + + assert html |> find(~s|a[href^="/team/setup"]|) |> LazyHTML.attribute("href") == [ + "/team/setup?__team=none" + ] + end + + test "renders the 'Create new team' option for the existing personal team when another team is selected", + %{conn: conn, user: user} do + site = new_site(owner: user) + other_team = insert(:team, name: "Zeta", identifier: Ecto.UUID.generate()) + add_member(other_team, user: user, role: :viewer) + conn = get(set_current_team(conn, other_team), ~p"/settings/preferences") html = html_response(conn, 200) - refute element_exists?(html, ~s/[data-test="create-a-team-cta"]/) + + assert html |> find(~s|a[href^="/team/setup"]|) |> LazyHTML.attribute("href") == [ + "/team/setup?__team=#{site.team.identifier}" + ] + end + end + + on_ee do + describe "account dropdown menu (_header.html) - SSO user" do + setup [:create_user, :create_site, :create_team, :setup_sso, :provision_sso_user, :log_in] + + test "does not render the 'Create new team' option", %{conn: conn} do + conn = get(conn, ~p"/settings/preferences") + html = html_response(conn, 200) + refute element_exists?(html, ~s/[data-test="create-a-team-cta"]/) + end end end