From ef17f104384b28873c81eb97230f8e7f4ce392f6 Mon Sep 17 00:00:00 2001 From: Adrian Gruntkowski Date: Thu, 6 Feb 2025 13:11:42 +0100 Subject: [PATCH] Implement basic team switcher --- lib/plausible/teams/users.ex | 22 +++++++ .../controllers/auth_controller.ex | 66 ++++++++++++++++++- lib/plausible_web/live/auth_context.ex | 5 ++ lib/plausible_web/plugs/auth_plug.ex | 1 + lib/plausible_web/router.ex | 3 + .../templates/auth/select_team.html.heex | 29 ++++++++ .../templates/layout/_header.html.heex | 12 ++++ 7 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 lib/plausible_web/templates/auth/select_team.html.heex diff --git a/lib/plausible/teams/users.ex b/lib/plausible/teams/users.ex index 55fe12da6479..a36bbf6e151c 100644 --- a/lib/plausible/teams/users.ex +++ b/lib/plausible/teams/users.ex @@ -20,6 +20,28 @@ defmodule Plausible.Teams.Users do ) end + def teams(user) do + from( + tm in Teams.Membership, + inner_join: t in assoc(tm, :team), + where: tm.user_id == ^user.id, + where: tm.role != :guest, + select: t, + order_by: [t.name, t.id] + ) + |> Repo.all() + |> Repo.preload(:owners) + end + + def teams_count(user) do + from( + tm in Teams.Membership, + where: tm.user_id == ^user.id, + where: tm.role != :guest + ) + |> Repo.aggregate(:count) + end + def team_member?(user, opts \\ []) do excluded_team_ids = Keyword.get(opts, :except, []) diff --git a/lib/plausible_web/controllers/auth_controller.ex b/lib/plausible_web/controllers/auth_controller.ex index 47e90b1f3aad..7e989ba60e27 100644 --- a/lib/plausible_web/controllers/auth_controller.ex +++ b/lib/plausible_web/controllers/auth_controller.ex @@ -3,6 +3,7 @@ defmodule PlausibleWeb.AuthController do use Plausible.Repo alias Plausible.Auth + alias Plausible.Teams alias PlausibleWeb.TwoFactor alias PlausibleWeb.UserAuth @@ -33,7 +34,9 @@ defmodule PlausibleWeb.AuthController do :verify_2fa_setup_form, :verify_2fa_setup, :disable_2fa, - :generate_2fa_recovery_codes + :generate_2fa_recovery_codes, + :select_team, + :switch_team ] ) @@ -52,6 +55,67 @@ defmodule PlausibleWeb.AuthController do TwoFactor.Session.clear_2fa_user(conn) end + def select_team(conn, _params) do + current_user = conn.assigns.current_user + + owner_name_fn = fn owner -> + if owner.id == current_user.id do + "You" + else + owner.name + end + end + + teams = + current_user + |> Teams.Users.teams() + |> Enum.map(fn team -> + current_team? = team.id == conn.assigns.current_team.id + + owners = + Enum.map_join(team.owners, ", ", &owner_name_fn.(&1)) + + many_owners? = length(team.owners) > 1 + + %{ + identifier: team.identifier, + name: team.name, + current?: current_team?, + many_owners?: many_owners?, + owners: owners + } + end) + + render(conn, "select_team.html", teams: teams) + end + + def switch_team(conn, params) do + current_user = conn.assigns.current_user + team = Teams.get(params["team_id"]) + + if team do + case Teams.Memberships.team_role(team, current_user) do + {:ok, role} when role != :guest -> + conn + |> put_session("current_team_id", team.identifier) + |> put_flash( + :success, + "You have switched to \"#{conn.assigns.current_team.name}\" team" + ) + |> redirect(to: Routes.site_path(conn, :index)) + + _ -> + conn + |> put_flash(:error, "You have select an invalid team") + |> redirect(to: Routes.site_path(conn, :index)) + end + else + conn + |> put_flash(:error, "You have select an invalid team") + |> redirect(to: Routes.site_path(conn, :index)) + end + end + def activate_form(conn, params) do user = conn.assigns.current_user flow = params["flow"] || PlausibleWeb.Flows.register() diff --git a/lib/plausible_web/live/auth_context.ex b/lib/plausible_web/live/auth_context.ex index 4d2f877d44de..5c69e2248e0b 100644 --- a/lib/plausible_web/live/auth_context.ex +++ b/lib/plausible_web/live/auth_context.ex @@ -58,6 +58,11 @@ defmodule PlausibleWeb.Live.AuthContext do |> assign_new(:current_team, fn context -> context.team_from_session || context.my_team end) + |> assign_new(:multiple_teams?, fn context -> + if context.current_user do + Teams.Users.teams_count(context.current_user) > 1 + end + end) {:cont, socket} end diff --git a/lib/plausible_web/plugs/auth_plug.ex b/lib/plausible_web/plugs/auth_plug.ex index 24f684cdccc5..36ff83af481b 100644 --- a/lib/plausible_web/plugs/auth_plug.ex +++ b/lib/plausible_web/plugs/auth_plug.ex @@ -49,6 +49,7 @@ defmodule PlausibleWeb.AuthPlug do |> assign(:current_user_session, user_session) |> assign(:my_team, my_team) |> assign(:current_team, current_team || my_team) + |> assign(:multiple_teams?, Teams.Users.teams_count(user) > 1) _ -> conn diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex index 5e8ca943bb2b..00e1356089fc 100644 --- a/lib/plausible_web/router.ex +++ b/lib/plausible_web/router.ex @@ -400,6 +400,9 @@ defmodule PlausibleWeb.Router do get "/logout", AuthController, :logout delete "/me", AuthController, :delete_me + get "/team/select", AuthController, :select_team + post "/team/select/:team_id", AuthController, :switch_team + get "/auth/google/callback", AuthController, :google_auth_callback on_ee do diff --git a/lib/plausible_web/templates/auth/select_team.html.heex b/lib/plausible_web/templates/auth/select_team.html.heex new file mode 100644 index 000000000000..abadc7ca3f9e --- /dev/null +++ b/lib/plausible_web/templates/auth/select_team.html.heex @@ -0,0 +1,29 @@ +<.focus_box> + <:title>Switch Team + + <:subtitle>Switch your current team. + +
+ +
+ diff --git a/lib/plausible_web/templates/layout/_header.html.heex b/lib/plausible_web/templates/layout/_header.html.heex index f699cb1259e7..a580fd01c219 100644 --- a/lib/plausible_web/templates/layout/_header.html.heex +++ b/lib/plausible_web/templates/layout/_header.html.heex @@ -72,6 +72,18 @@ {@conn.assigns[:current_user].email}

+ <.dropdown_item :if={@conn.assigns[:multiple_teams?]}> +
Team
+

+ {@current_team.name} +

+ + <.dropdown_item + :if={@conn.assigns[:multiple_teams?]} + href={Routes.auth_path(@conn, :select_team)} + > + Switch Team + <.dropdown_divider /> <.dropdown_item href={Routes.settings_path(@conn, :index)}> Account Settings