Starter Tier: Teams UI follow-ups (#5456)

* keep teams feature explicitly for UI

* keep devsubscriptions in sync with prod

On prod, when a subscription is created without a current team in assings,
a new team is force created for that user.

* disable team creation when no point to create it

* fix ce_test compile warning

* fix tests on CE

* Update lib/plausible/teams/billing.ex

Co-authored-by: hq1 <hq@mtod.org>

* add solo team in seeds

* fix top border blur + stop autofocusing input when blurred

---------

Co-authored-by: hq1 <hq@mtod.org>
This commit is contained in:
RobertJoonas 2025-06-03 11:14:50 +01:00 committed by GitHub
parent a01f8e1c05
commit 38f1de6ecd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 136 additions and 251 deletions

View File

@ -72,7 +72,6 @@ defmodule Plausible.Billing.Feature do
@features [ @features [
Plausible.Billing.Feature.Props, Plausible.Billing.Feature.Props,
Plausible.Billing.Feature.Teams,
Plausible.Billing.Feature.SharedLinks, Plausible.Billing.Feature.SharedLinks,
Plausible.Billing.Feature.Funnels, Plausible.Billing.Feature.Funnels,
Plausible.Billing.Feature.Goals, Plausible.Billing.Feature.Goals,
@ -200,13 +199,6 @@ defmodule Plausible.Billing.Feature.Props do
toggle_field: :props_enabled toggle_field: :props_enabled
end end
defmodule Plausible.Billing.Feature.Teams do
@moduledoc false
use Plausible.Billing.Feature,
name: :teams,
display_name: "Team Management"
end
defmodule Plausible.Billing.Feature.SharedLinks do defmodule Plausible.Billing.Feature.SharedLinks do
@moduledoc false @moduledoc false
use Plausible.Billing.Feature, use Plausible.Billing.Feature,
@ -238,3 +230,19 @@ defmodule Plausible.Billing.Feature.SitesAPI do
name: :sites_api, name: :sites_api,
display_name: "Sites API" display_name: "Sites API"
end end
defmodule Plausible.Billing.Feature.Teams do
@moduledoc """
Unlike other feature modules, this one only exists to make feature gating
settings views more convenient. Other than that, it's not even considered
a feature on its own. The real access to "Teams" is controlled by the
team member limit.
"""
def check_availability(team) do
if Plausible.Teams.Billing.solo?(team) do
{:error, :upgrade_required}
else
:ok
end
end
end

View File

@ -235,8 +235,16 @@ defmodule Plausible.Teams.Billing do
nil -> @team_member_limit_for_trials nil -> @team_member_limit_for_trials
end end
end end
def solo?(nil), do: true
def solo?(team) do
team_member_limit(team) == 0
end
else else
def team_member_limit(_team), do: :unlimited def team_member_limit(_team), do: :unlimited
def solo?(_team), do: false
end end
@doc """ @doc """
@ -605,19 +613,19 @@ defmodule Plausible.Teams.Billing do
case Plans.get_subscription_plan(team.subscription) do case Plans.get_subscription_plan(team.subscription) do
%EnterprisePlan{features: features} -> %EnterprisePlan{features: features} ->
features ++ [Feature.Teams, SharedLinks] features ++ [SharedLinks]
%Plan{features: features} -> %Plan{features: features} ->
features features
:free_10k -> :free_10k ->
[Goals, Props, StatsAPI, Feature.Teams, SharedLinks] [Goals, Props, StatsAPI, SharedLinks]
nil -> nil ->
if Teams.on_trial?(team) do if Teams.on_trial?(team) do
Feature.list() -- [SitesAPI] Feature.list() -- [SitesAPI]
else else
[Goals, Feature.Teams, SharedLinks] [Goals, SharedLinks]
end end
end end
end end

View File

@ -5,21 +5,14 @@ defmodule PlausibleWeb.Components.Billing do
use Plausible use Plausible
require Plausible.Billing.Subscription.Status require Plausible.Billing.Subscription.Status
alias Plausible.Billing.{Subscription, Subscriptions, Feature, Plan, Plans, EnterprisePlan} alias Plausible.Billing.{Subscription, Subscriptions, Plan, Plans, EnterprisePlan}
attr :current_role, :atom, required: true attr :current_role, :atom, required: true
attr :current_team, :any, required: true attr :current_team, :any, required: true
attr :feature_mod, :atom, required: true, values: Feature.list() attr :locked?, :boolean, required: true
slot :inner_block, required: true slot :inner_block, required: true
def feature_gate(assigns) do def feature_gate(assigns) do
assigns =
assign(
assigns,
:locked?,
assigns.feature_mod.check_availability(assigns.current_team) != :ok
)
~H""" ~H"""
<div id="feature-gate-inner-block-container" class={if(@locked?, do: "pointer-events-none")}> <div id="feature-gate-inner-block-container" class={if(@locked?, do: "pointer-events-none")}>
{render_slot(@inner_block)} {render_slot(@inner_block)}

View File

@ -62,7 +62,8 @@ defmodule PlausibleWeb.Components.Billing.PlanBenefits do
[ [
"Everything in Starter", "Everything in Starter",
site_limit_benefit(growth_plan), site_limit_benefit(growth_plan),
team_member_limit_benefit(growth_plan) team_member_limit_benefit(growth_plan),
"Team Management"
] ]
|> Kernel.++(feature_benefits(growth_plan)) |> Kernel.++(feature_benefits(growth_plan))
|> Kernel.--(starter_benefits) |> Kernel.--(starter_benefits)

View File

@ -472,7 +472,7 @@ defmodule PlausibleWeb.Components.Generic do
<div class="relative"> <div class="relative">
<%= if @feature_mod do %> <%= if @feature_mod do %>
<PlausibleWeb.Components.Billing.feature_gate <PlausibleWeb.Components.Billing.feature_gate
feature_mod={@feature_mod} locked?={@feature_mod.check_availability(@current_team) != :ok}
current_role={@current_role} current_role={@current_role}
current_team={@current_team} current_team={@current_team}
> >
@ -634,6 +634,7 @@ defmodule PlausibleWeb.Components.Generic do
slot :subtitle slot :subtitle
slot :inner_block, required: true slot :inner_block, required: true
slot :footer slot :footer
attr :padding?, :boolean, default: true
attr :rest, :global attr :rest, :global
def focus_box(assigns) do def focus_box(assigns) do
@ -642,7 +643,7 @@ defmodule PlausibleWeb.Components.Generic do
class="bg-white w-full max-w-lg mx-auto dark:bg-gray-800 text-gray-900 dark:text-gray-100 shadow-md rounded-md mt-12" class="bg-white w-full max-w-lg mx-auto dark:bg-gray-800 text-gray-900 dark:text-gray-100 shadow-md rounded-md mt-12"
{@rest} {@rest}
> >
<div class="p-8"> <div class={if(@padding?, do: "p-8")}>
<.title :if={@title != []}> <.title :if={@title != []}>
{render_slot(@title)} {render_slot(@title)}
</.title> </.title>

View File

@ -55,10 +55,12 @@ defmodule PlausibleWeb.Live.TeamSetup do
end end
def render(assigns) do def render(assigns) do
assigns = assign(assigns, :locked?, Plausible.Teams.Billing.solo?(assigns.current_team))
~H""" ~H"""
<.focus_box> <.focus_box padding?={false}>
<:title> <:title>
<div class="flex justify-between"> <div class="pt-8 px-8 flex justify-between">
<div>Create a new team</div> <div>Create a new team</div>
<div class="ml-auto"> <div class="ml-auto">
<.docs_info slug="users-roles" /> <.docs_info slug="users-roles" />
@ -66,9 +68,17 @@ defmodule PlausibleWeb.Live.TeamSetup do
</div> </div>
</:title> </:title>
<:subtitle> <:subtitle>
<p class="px-8">
Name your team, add team members and assign roles. When ready, click "Create Team" to send invitations Name your team, add team members and assign roles. When ready, click "Create Team" to send invitations
</p>
</:subtitle> </:subtitle>
<div class="relative -mt-8 pt-4 pb-8 px-8">
<PlausibleWeb.Components.Billing.feature_gate
current_role={@current_team_role}
current_team={@current_team}
locked?={@locked?}
>
<.form <.form
:let={f} :let={f}
for={@team_name_form} for={@team_name_form}
@ -81,7 +91,7 @@ defmodule PlausibleWeb.Live.TeamSetup do
<.input <.input
type="text" type="text"
placeholder={"#{@current_user.name}'s Team"} placeholder={"#{@current_user.name}'s Team"}
autofocus autofocus={not @locked?}
field={f[:name]} field={f[:name]}
label="Name" label="Name"
width="w-full" width="w-full"
@ -99,6 +109,8 @@ defmodule PlausibleWeb.Live.TeamSetup do
"mode" => "team-setup" "mode" => "team-setup"
} }
)} )}
</PlausibleWeb.Components.Billing.feature_gate>
</div>
</.focus_box> </.focus_box>
""" """
end end

View File

@ -92,10 +92,7 @@
Account Settings Account Settings
</.dropdown_item> </.dropdown_item>
<div :if={ <div :if={@my_team && @my_team.id == @current_team.id}>
@my_team && @my_team.id == @current_team.id &&
Plausible.Billing.Feature.Teams.check_availability(@current_team) == :ok
}>
<.dropdown_item class="flex" href={Routes.team_setup_path(@conn, :setup)}> <.dropdown_item class="flex" href={Routes.team_setup_path(@conn, :setup)}>
<span data-test="create-a-team-cta" class="flex-1"> <span data-test="create-a-team-cta" class="flex-1">
Create a Team Create a Team

View File

@ -1,5 +1,5 @@
<.settings_tiles> <.settings_tiles>
<%= if not Plausible.Teams.setup?(@site.team) and Plausible.Billing.Feature.Teams.check_availability(@site.team) == :ok do %> <%= if not Plausible.Teams.setup?(@site.team) and not Plausible.Teams.Billing.solo?(@site.team) do %>
<PlausibleWeb.Team.Notice.owner_cta_banner :if={@site_role == :owner} /> <PlausibleWeb.Team.Notice.owner_cta_banner :if={@site_role == :owner} />
<PlausibleWeb.Team.Notice.guest_cta_banner :if={@site_role != :owner} /> <PlausibleWeb.Team.Notice.guest_cta_banner :if={@site_role != :owner} />
<% end %> <% end %>

View File

@ -11,7 +11,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -27,7 +26,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -43,7 +41,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -59,7 +56,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -75,7 +71,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -91,7 +86,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -107,7 +101,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -123,7 +116,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -139,7 +131,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -155,7 +146,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
} }

View File

@ -11,7 +11,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -27,7 +26,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -43,7 +41,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -59,7 +56,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -75,7 +71,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -91,7 +86,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -107,7 +101,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -123,7 +116,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -139,7 +131,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -155,7 +146,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
} }

View File

@ -11,7 +11,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -27,7 +26,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -43,7 +41,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -59,7 +56,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -75,7 +71,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -91,7 +86,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -107,7 +101,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -123,7 +116,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -142,7 +134,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -161,7 +152,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -180,7 +170,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -199,7 +188,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -218,7 +206,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -237,7 +224,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -256,7 +242,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -275,7 +260,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
} }

View File

@ -9,7 +9,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -24,7 +23,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -39,7 +37,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -54,7 +51,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -69,7 +65,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -84,7 +79,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -99,7 +93,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -114,7 +107,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -134,7 +126,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -154,7 +145,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -174,7 +164,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -194,7 +183,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -214,7 +202,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -234,7 +221,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -254,7 +240,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -274,7 +259,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5

View File

@ -113,7 +113,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -129,7 +128,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -145,7 +143,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -161,7 +158,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -177,7 +173,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -193,7 +188,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -209,7 +203,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -225,7 +218,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -241,7 +233,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -261,7 +252,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -281,7 +271,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -301,7 +290,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -321,7 +309,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -341,7 +328,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -361,7 +347,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -381,7 +366,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",

View File

@ -13,6 +13,8 @@ use Plausible
import Plausible.Teams.Test import Plausible.Teams.Test
FunWithFlags.enable(:starter_tier)
words = words =
for i <- 0..(:erlang.system_info(:atom_count) - 1), for i <- 0..(:erlang.system_info(:atom_count) - 1),
do: :erlang.binary_to_term(<<131, 75, i::24>>) do: :erlang.binary_to_term(<<131, 75, i::24>>)
@ -66,6 +68,11 @@ user2 = new_user(name: "Mary Jane", email: "user2@plausible.test", password: "pl
site2 = new_site(domain: "computer.example.com", owner: user2) site2 = new_site(domain: "computer.example.com", owner: user2)
invite_guest(site2, user, inviter: user2, role: :viewer) invite_guest(site2, user, inviter: user2, role: :viewer)
solo_user = new_user(name: "Solo User", email: "solo@plausible.test", password: "plausible")
new_site(domain: "mysolosite.com", owner: solo_user)
{:ok, solo_team} = Plausible.Teams.get_or_create(solo_user)
Plausible.Billing.DevSubscriptions.create(solo_team.id, "910413")
Plausible.Factory.insert_list(29, :ip_rule, site: site) Plausible.Factory.insert_list(29, :ip_rule, site: site)
Plausible.Factory.insert(:country_rule, site: site, country_code: "PL") Plausible.Factory.insert(:country_rule, site: site, country_code: "PL")
Plausible.Factory.insert(:country_rule, site: site, country_code: "EE") Plausible.Factory.insert(:country_rule, site: site, country_code: "EE")

View File

@ -11,7 +11,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -27,7 +26,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -43,7 +41,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -59,7 +56,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -75,7 +71,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -91,7 +86,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -107,7 +101,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -123,7 +116,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -139,7 +131,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -155,7 +146,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
} }

View File

@ -11,7 +11,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -27,7 +26,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -43,7 +41,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -59,7 +56,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -75,7 +71,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -91,7 +86,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -107,7 +101,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -123,7 +116,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -139,7 +131,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -155,7 +146,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
} }

View File

@ -11,7 +11,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -27,7 +26,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -43,7 +41,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -59,7 +56,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -75,7 +71,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -91,7 +86,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -107,7 +101,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -123,7 +116,6 @@
"goals", "goals",
"props", "props",
"stats_api", "stats_api",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -142,7 +134,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -161,7 +152,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -180,7 +170,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -199,7 +188,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -218,7 +206,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -237,7 +224,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -256,7 +242,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
}, },
@ -275,7 +260,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
] ]
} }

View File

@ -9,7 +9,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -24,7 +23,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -39,7 +37,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -54,7 +51,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -69,7 +65,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -84,7 +79,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -99,7 +93,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -114,7 +107,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 3 "data_retention_in_years": 3
@ -134,7 +126,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -154,7 +145,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -174,7 +164,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -194,7 +183,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -214,7 +202,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -234,7 +221,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -254,7 +240,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5
@ -274,7 +259,6 @@
"funnels", "funnels",
"stats_api", "stats_api",
"site_segments", "site_segments",
"teams",
"shared_links" "shared_links"
], ],
"data_retention_in_years": 5 "data_retention_in_years": 5

View File

@ -113,7 +113,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -129,7 +128,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -145,7 +143,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -161,7 +158,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -177,7 +173,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -193,7 +188,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -209,7 +203,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -225,7 +218,6 @@
"team_member_limit": 3, "team_member_limit": 3,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments" "site_segments"
], ],
@ -241,7 +233,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -261,7 +252,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -281,7 +271,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -301,7 +290,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -321,7 +309,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -341,7 +328,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -361,7 +347,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",
@ -381,7 +366,6 @@
"team_member_limit": 10, "team_member_limit": 10,
"features": [ "features": [
"goals", "goals",
"teams",
"shared_links", "shared_links",
"site_segments", "site_segments",
"props", "props",

View File

@ -30,11 +30,6 @@ defmodule Plausible.Billing.FeatureTest do
end end
end end
test "Plausible.Billing.Feature.Teams.check_availability/2 returns :ok when user is on an enterprise plan" do
team = new_user() |> subscribe_to_enterprise_plan() |> team_of()
assert :ok == Plausible.Billing.Feature.Teams.check_availability(team)
end
test "Plausible.Billing.Feature.SharedLinks.check_availability/2 returns :ok when user is on an enterprise plan" do test "Plausible.Billing.Feature.SharedLinks.check_availability/2 returns :ok when user is on an enterprise plan" do
team = new_user() |> subscribe_to_enterprise_plan() |> team_of() team = new_user() |> subscribe_to_enterprise_plan() |> team_of()
assert :ok == Plausible.Billing.Feature.SharedLinks.check_availability(team) assert :ok == Plausible.Billing.Feature.SharedLinks.check_availability(team)

View File

@ -3,7 +3,7 @@ defmodule Plausible.Billing.QuotaTest do
use Plausible.DataCase, async: true use Plausible.DataCase, async: true
use Plausible use Plausible
alias Plausible.Billing.{Quota, Plans} alias Plausible.Billing.{Quota, Plans}
alias Plausible.Billing.Feature.{Goals, Props, SitesAPI, StatsAPI, Teams, SharedLinks} alias Plausible.Billing.Feature.{Goals, Props, SitesAPI, StatsAPI, SharedLinks}
use Plausible.Teams.Test use Plausible.Teams.Test
@ -575,24 +575,18 @@ defmodule Plausible.Billing.QuotaTest do
test "users with expired trials have no access to subscription features" do test "users with expired trials have no access to subscription features" do
team = new_user(trial_expiry_date: ~D[2023-01-01]) |> team_of() team = new_user(trial_expiry_date: ~D[2023-01-01]) |> team_of()
assert [Goals, Plausible.Billing.Feature.Teams, Plausible.Billing.Feature.SharedLinks] == assert [Goals, Plausible.Billing.Feature.SharedLinks] ==
Plausible.Teams.Billing.allowed_features_for(team) Plausible.Teams.Billing.allowed_features_for(team)
end end
end end
test "returns all grandfathered features when user is on an old plan" do for {generation, plan_id} <- [{"v1", @v1_plan_id}, {"v2", @v2_plan_id}, {"v3", @v3_plan_id}] do
team_on_v1 = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of() test "returns all grandfathered features when user is on a #{generation} plan" do
team_on_v2 = new_user() |> subscribe_to_plan(@v2_plan_id) |> team_of() team = new_user() |> subscribe_to_plan(unquote(plan_id)) |> team_of()
team_on_v3 = new_user() |> subscribe_to_plan(@v3_plan_id) |> team_of()
assert [Goals, Props, StatsAPI, Teams, SharedLinks] == assert [Goals, Props, StatsAPI, SharedLinks] ==
Plausible.Teams.Billing.allowed_features_for(team_on_v1) Plausible.Teams.Billing.allowed_features_for(team)
end
assert [Goals, Props, StatsAPI, Teams, SharedLinks] ==
Plausible.Teams.Billing.allowed_features_for(team_on_v2)
assert [Goals, Props, StatsAPI, Teams, SharedLinks] ==
Plausible.Teams.Billing.allowed_features_for(team_on_v3)
end end
test "returns features for a free_10k plan" do test "returns features for a free_10k plan" do
@ -600,7 +594,7 @@ defmodule Plausible.Billing.QuotaTest do
subscribe_to_plan(user, "free_10k") subscribe_to_plan(user, "free_10k")
team = team_of(user) team = team_of(user)
assert [Goals, Props, StatsAPI, Teams, SharedLinks] == assert [Goals, Props, StatsAPI, SharedLinks] ==
Plausible.Teams.Billing.allowed_features_for(team) Plausible.Teams.Billing.allowed_features_for(team)
end end
@ -619,7 +613,6 @@ defmodule Plausible.Billing.QuotaTest do
assert [ assert [
Plausible.Billing.Feature.StatsAPI, Plausible.Billing.Feature.StatsAPI,
Plausible.Billing.Feature.Funnels, Plausible.Billing.Feature.Funnels,
Plausible.Billing.Feature.Teams,
Plausible.Billing.Feature.SharedLinks Plausible.Billing.Feature.SharedLinks
] == ] ==
Plausible.Teams.Billing.allowed_features_for(team) Plausible.Teams.Billing.allowed_features_for(team)
@ -641,7 +634,7 @@ defmodule Plausible.Billing.QuotaTest do
team = team_of(user) team = team_of(user)
assert [Goals, Props, StatsAPI, Teams, SharedLinks] == assert [Goals, Props, StatsAPI, SharedLinks] ==
Plausible.Teams.Billing.allowed_features_for(team) Plausible.Teams.Billing.allowed_features_for(team)
end end
@ -669,7 +662,6 @@ defmodule Plausible.Billing.QuotaTest do
assert [ assert [
Plausible.Billing.Feature.StatsAPI, Plausible.Billing.Feature.StatsAPI,
Plausible.Billing.Feature.Teams,
Plausible.Billing.Feature.SharedLinks Plausible.Billing.Feature.SharedLinks
] == ] ==
Plausible.Teams.Billing.allowed_features_for(team) Plausible.Teams.Billing.allowed_features_for(team)
@ -687,7 +679,6 @@ defmodule Plausible.Billing.QuotaTest do
assert [ assert [
Plausible.Billing.Feature.StatsAPI, Plausible.Billing.Feature.StatsAPI,
Plausible.Billing.Feature.SitesAPI, Plausible.Billing.Feature.SitesAPI,
Plausible.Billing.Feature.Teams,
Plausible.Billing.Feature.SharedLinks Plausible.Billing.Feature.SharedLinks
] == ] ==
Plausible.Teams.Billing.allowed_features_for(team) Plausible.Teams.Billing.allowed_features_for(team)

View File

@ -13,7 +13,7 @@ defmodule PlausibleWeb.Components.BillingTest do
%{ %{
current_role: :owner, current_role: :owner,
current_team: user |> subscribe_to_growth_plan() |> team_of(), current_team: user |> subscribe_to_growth_plan() |> team_of(),
feature_mod: Plausible.Billing.Feature.Props locked?: true
} }
|> render_feature_gate() |> render_feature_gate()
@ -28,7 +28,7 @@ defmodule PlausibleWeb.Components.BillingTest do
%{ %{
current_role: nil, current_role: nil,
current_team: nil, current_team: nil,
feature_mod: Plausible.Billing.Feature.Props locked?: true
} }
|> render_feature_gate() |> render_feature_gate()
@ -43,7 +43,7 @@ defmodule PlausibleWeb.Components.BillingTest do
%{ %{
current_role: :owner, current_role: :owner,
current_team: user |> subscribe_to_business_plan() |> team_of(), current_team: user |> subscribe_to_business_plan() |> team_of(),
feature_mod: Plausible.Billing.Feature.Funnels locked?: false
} }
|> render_feature_gate() |> render_feature_gate()
@ -58,7 +58,7 @@ defmodule PlausibleWeb.Components.BillingTest do
%{ %{
current_role: :owner, current_role: :owner,
current_team: user |> subscribe_to_growth_plan() |> team_of(), current_team: user |> subscribe_to_growth_plan() |> team_of(),
feature_mod: Plausible.Billing.Feature.Props locked?: true
} }
|> render_feature_gate() |> render_feature_gate()
@ -70,7 +70,7 @@ defmodule PlausibleWeb.Components.BillingTest do
%{ %{
current_role: :billing, current_role: :billing,
current_team: user |> subscribe_to_growth_plan() |> team_of(), current_team: user |> subscribe_to_growth_plan() |> team_of(),
feature_mod: Plausible.Billing.Feature.Props locked?: true
} }
|> render_feature_gate() |> render_feature_gate()
@ -85,7 +85,7 @@ defmodule PlausibleWeb.Components.BillingTest do
%{ %{
current_role: :editor, current_role: :editor,
current_team: user |> subscribe_to_growth_plan() |> team_of(), current_team: user |> subscribe_to_growth_plan() |> team_of(),
feature_mod: Plausible.Billing.Feature.Props locked?: true
} }
|> render_feature_gate() |> render_feature_gate()
@ -94,10 +94,11 @@ defmodule PlausibleWeb.Components.BillingTest do
end end
defp render_feature_gate(assigns) do defp render_feature_gate(assigns) do
rendered_to_string(~H""" ~H"""
<PlausibleWeb.Components.Billing.feature_gate {assigns}> <PlausibleWeb.Components.Billing.feature_gate {assigns}>
<div>content...</div> <div>content...</div>
</PlausibleWeb.Components.Billing.feature_gate> </PlausibleWeb.Components.Billing.feature_gate>
""") """
|> rendered_to_string()
end end
end end

View File

@ -263,7 +263,7 @@ defmodule PlausibleWeb.AdminControllerTest do
conn = get(conn, "/crm/billing/team/#{team.id}/current_plan") conn = get(conn, "/crm/billing/team/#{team.id}/current_plan")
assert json_response(conn, 200) == %{ assert json_response(conn, 200) == %{
"features" => ["goals", "teams", "shared_links"], "features" => ["goals", "shared_links"],
"monthly_pageview_limit" => 10_000_000, "monthly_pageview_limit" => 10_000_000,
"site_limit" => 10, "site_limit" => 10,
"team_member_limit" => 3 "team_member_limit" => 3

View File

@ -1421,16 +1421,6 @@ defmodule PlausibleWeb.SettingsControllerTest do
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 a Team"
end end
test "does not render the 'Create a Team' option if Teams feature is unavailable", %{
conn: conn,
user: user
} do
subscribe_to_starter_plan(user)
conn = get(conn, Routes.settings_path(conn, :preferences))
html = html_response(conn, 200)
refute element_exists?(html, ~s/[data-test="create-a-team-cta"]/)
end
test "does not render the 'Create a Team' option if a team is already set up", %{ test "does not render the 'Create a Team' option if a team is already set up", %{
conn: conn, conn: conn,
user: user user: user

View File

@ -699,6 +699,7 @@ defmodule PlausibleWeb.SiteControllerTest do
refute resp =~ "Team members automatically have access to this site." refute resp =~ "Team members automatically have access to this site."
end end
@tag :ee_only
test "does not render team management notice if Teams feature unavailable", %{ test "does not render team management notice if Teams feature unavailable", %{
conn: conn, conn: conn,
user: user user: user

View File

@ -128,7 +128,6 @@ defmodule PlausibleWeb.Live.CustomerSupport.TeamsTest do
"false", "false",
"false", "false",
"false", "false",
"teams",
"false", "false",
"shared_links", "shared_links",
"false", "false",
@ -150,7 +149,6 @@ defmodule PlausibleWeb.Live.CustomerSupport.TeamsTest do
%Plausible.Billing.EnterprisePlan{ %Plausible.Billing.EnterprisePlan{
billing_interval: :yearly, billing_interval: :yearly,
features: [ features: [
Plausible.Billing.Feature.Teams,
Plausible.Billing.Feature.SharedLinks, Plausible.Billing.Feature.SharedLinks,
Plausible.Billing.Feature.SitesAPI Plausible.Billing.Feature.SitesAPI
], ],

View File

@ -83,6 +83,21 @@ defmodule PlausibleWeb.Live.TeamSetupTest do
_ = render(lv) _ = render(lv)
assert Repo.reload!(team).name == "Team Name 1" assert Repo.reload!(team).name == "Team Name 1"
end end
@tag :ee_only
test "blurs UI with an upgrade CTA if the subscription team member limit is 0", %{
conn: conn,
user: user
} do
subscribe_to_starter_plan(user)
{:ok, _lv, html} = live(conn, @url)
assert class_of_element(html, "#feature-gate-inner-block-container") =~
"pointer-events-none"
assert class_of_element(html, "#feature-gate-overlay") =~ "backdrop-blur-[6px]"
end
end end
describe "/team/setup - full integration" do describe "/team/setup - full integration" do

View File

@ -32,7 +32,6 @@ defmodule PlausibleWeb.Plugins.API.Controllers.CapabilitiesTest do
"StatsAPI" => false, "StatsAPI" => false,
"SitesAPI" => false, "SitesAPI" => false,
"SiteSegments" => false, "SiteSegments" => false,
"Teams" => false,
"SharedLinks" => false "SharedLinks" => false
} }
} }
@ -60,7 +59,6 @@ defmodule PlausibleWeb.Plugins.API.Controllers.CapabilitiesTest do
"StatsAPI" => false, "StatsAPI" => false,
"SitesAPI" => false, "SitesAPI" => false,
"SiteSegments" => false, "SiteSegments" => false,
"Teams" => false,
"SharedLinks" => false "SharedLinks" => false
} }
} }
@ -90,7 +88,6 @@ defmodule PlausibleWeb.Plugins.API.Controllers.CapabilitiesTest do
"StatsAPI" => true, "StatsAPI" => true,
"SitesAPI" => false, "SitesAPI" => false,
"SiteSegments" => true, "SiteSegments" => true,
"Teams" => true,
"SharedLinks" => true "SharedLinks" => true
} }
} }
@ -122,7 +119,6 @@ defmodule PlausibleWeb.Plugins.API.Controllers.CapabilitiesTest do
"StatsAPI" => false, "StatsAPI" => false,
"SitesAPI" => false, "SitesAPI" => false,
"SiteSegments" => false, "SiteSegments" => false,
"Teams" => true,
"SharedLinks" => true "SharedLinks" => true
} }
} }
@ -157,7 +153,6 @@ defmodule PlausibleWeb.Plugins.API.Controllers.CapabilitiesTest do
"StatsAPI" => true, "StatsAPI" => true,
"SitesAPI" => true, "SitesAPI" => true,
"SiteSegments" => false, "SiteSegments" => false,
"Teams" => true,
"SharedLinks" => true "SharedLinks" => true
} }
} }

View File

@ -5,6 +5,9 @@ defmodule PlausibleWeb.DevSubscriptionController do
use PlausibleWeb, :controller use PlausibleWeb, :controller
alias Plausible.Billing.DevSubscriptions alias Plausible.Billing.DevSubscriptions
alias Plausible.Auth.User
alias Plausible.Teams.Team
alias Plausible.Teams
plug PlausibleWeb.RequireAccountPlug plug PlausibleWeb.RequireAccountPlug
@ -18,7 +21,7 @@ defmodule PlausibleWeb.DevSubscriptionController do
end end
def update_form(conn, _params) do def update_form(conn, _params) do
team = conn.assigns.current_team |> Plausible.Teams.with_subscription() team = conn.assigns.current_team |> Teams.with_subscription()
if is_nil(team.subscription), if is_nil(team.subscription),
do: raise("Can't render subscription update form without subscription") do: raise("Can't render subscription update form without subscription")
@ -30,20 +33,25 @@ defmodule PlausibleWeb.DevSubscriptionController do
end end
def cancel_form(conn, _params) do def cancel_form(conn, _params) do
team = conn.assigns.current_team |> Plausible.Teams.with_subscription() team = conn.assigns.current_team |> Teams.with_subscription()
if is_nil(team.subscription), if is_nil(team.subscription),
do: raise("Can't render subscription cancel form without subscription") do: raise("Can't render subscription cancel form without subscription")
render(conn, "cancel_dev_subscription.html", render(conn, "cancel_dev_subscription.html",
back_link: Routes.settings_path(conn, :subscription), back_link: Routes.settings_path(conn, :subscription),
enterprise_plan?: Plausible.Teams.Billing.enterprise_configured?(team) enterprise_plan?: Teams.Billing.enterprise_configured?(team)
) )
end end
def create(conn, %{"plan_id" => plan_id}) do def create(conn, %{"plan_id" => plan_id}) do
team = conn.assigns.current_team for_team =
DevSubscriptions.create_after_1s(team.id, plan_id) case conn.assigns do
%{current_team: %Team{} = team} -> team
%{current_user: %User{} = user} -> Teams.force_create_my_team(user)
end
DevSubscriptions.create_after_1s(for_team.id, plan_id)
redirect(conn, to: Routes.billing_path(PlausibleWeb.Endpoint, :upgrade_success)) redirect(conn, to: Routes.billing_path(PlausibleWeb.Endpoint, :upgrade_success))
end end