Clean up old upgrade page (dead code) (#5652)
* remove deprecated modules/fns * get rid of starter_tier flag * remove legacy? arg from plans
This commit is contained in:
parent
e3bef74cde
commit
17675af4d0
|
|
@ -32,32 +32,30 @@ defmodule Plausible.Billing.Plans do
|
|||
end
|
||||
end
|
||||
|
||||
defp starter_plans_for(subscription, legacy?) do
|
||||
defp starter_plans_for(subscription) do
|
||||
active_plan = get_regular_plan(subscription, only_non_expired: true)
|
||||
|
||||
case {legacy?, active_plan} do
|
||||
{true, _} -> []
|
||||
{_, %Plan{kind: :growth, generation: g}} when g <= 4 -> []
|
||||
{_, _} -> Enum.filter(plans_v5(), &(&1.kind == :starter))
|
||||
case active_plan do
|
||||
%Plan{kind: :growth, generation: g} when g <= 4 -> []
|
||||
_ -> Enum.filter(plans_v5(), &(&1.kind == :starter))
|
||||
end
|
||||
end
|
||||
|
||||
@spec growth_plans_for(Subscription.t(), boolean()) :: [Plan.t()]
|
||||
@spec growth_plans_for(Subscription.t()) :: [Plan.t()]
|
||||
@doc """
|
||||
Returns a list of growth plans available for the subscription to choose.
|
||||
|
||||
As new versions of plans are introduced, subscriptions which were on old plans can
|
||||
still choose from old plans.
|
||||
"""
|
||||
def growth_plans_for(subscription, legacy? \\ false) do
|
||||
def growth_plans_for(subscription) do
|
||||
owned_plan = get_regular_plan(subscription)
|
||||
|
||||
default_plans = if legacy?, do: plans_v4(), else: plans_v5()
|
||||
latest = plans_v5()
|
||||
|
||||
cond do
|
||||
is_nil(owned_plan) -> default_plans
|
||||
subscription && Subscriptions.expired?(subscription) -> default_plans
|
||||
owned_plan.kind == :business -> default_plans
|
||||
is_nil(owned_plan) -> latest
|
||||
subscription && Subscriptions.expired?(subscription) -> latest
|
||||
owned_plan.kind == :business -> latest
|
||||
owned_plan.generation == 1 -> plans_v1() |> drop_high_plans(owned_plan)
|
||||
owned_plan.generation == 2 -> plans_v2() |> drop_high_plans(owned_plan)
|
||||
owned_plan.generation == 3 -> plans_v3()
|
||||
|
|
@ -67,27 +65,24 @@ defmodule Plausible.Billing.Plans do
|
|||
|> Enum.filter(&(&1.kind == :growth))
|
||||
end
|
||||
|
||||
def business_plans_for(subscription, legacy? \\ false) do
|
||||
def business_plans_for(subscription) do
|
||||
owned_plan = get_regular_plan(subscription)
|
||||
|
||||
default_plans = if legacy?, do: plans_v4(), else: plans_v5()
|
||||
latest = plans_v5()
|
||||
|
||||
cond do
|
||||
subscription && Subscriptions.expired?(subscription) -> default_plans
|
||||
subscription && Subscriptions.expired?(subscription) -> latest
|
||||
owned_plan && owned_plan.generation <= 3 -> plans_v3()
|
||||
owned_plan && owned_plan.generation <= 4 -> plans_v4()
|
||||
true -> default_plans
|
||||
true -> latest
|
||||
end
|
||||
|> Enum.filter(&(&1.kind == :business))
|
||||
end
|
||||
|
||||
def available_plans_for(subscription, opts \\ []) do
|
||||
legacy? = Keyword.get(opts, :legacy?, false)
|
||||
|
||||
%{
|
||||
starter: starter_plans_for(subscription, legacy?) |> maybe_add_prices(opts),
|
||||
growth: growth_plans_for(subscription, legacy?) |> maybe_add_prices(opts),
|
||||
business: business_plans_for(subscription, legacy?) |> maybe_add_prices(opts)
|
||||
starter: starter_plans_for(subscription) |> maybe_add_prices(opts),
|
||||
growth: growth_plans_for(subscription) |> maybe_add_prices(opts),
|
||||
business: business_plans_for(subscription) |> maybe_add_prices(opts)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -82,19 +82,6 @@ defmodule Plausible.Billing.Quota do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
[DEPRECATED] Used in LegacyChoosePlan in order to suggest a tier
|
||||
when `starter_tier` flag is not enabled.
|
||||
"""
|
||||
def legacy_suggest_tier(usage, highest_growth, highest_business, owned_tier) do
|
||||
cond do
|
||||
not eligible_for_upgrade?(usage) -> nil
|
||||
usage_fits_plan?(usage, highest_growth) and owned_tier != :business -> :growth
|
||||
usage_fits_plan?(usage, highest_business) -> :business
|
||||
true -> :custom
|
||||
end
|
||||
end
|
||||
|
||||
defp usage_fits_plan?(usage, plan) do
|
||||
with :ok <- ensure_within_plan_limits(usage, plan),
|
||||
:ok <- ensure_feature_access(usage, plan) do
|
||||
|
|
|
|||
|
|
@ -21,42 +21,6 @@ defmodule Plausible.Teams.Billing do
|
|||
@typep last_30_days_usage() :: %{:last_30_days => Quota.usage_cycle()}
|
||||
@typep monthly_pageview_usage() :: Quota.cycles_usage() | last_30_days_usage()
|
||||
|
||||
@starter_tier_launch ~D[2025-06-11]
|
||||
def starter_tier_launch(), do: @starter_tier_launch
|
||||
|
||||
def show_new_upgrade_page?(nil = _team) do
|
||||
FunWithFlags.enabled?(:starter_tier)
|
||||
end
|
||||
|
||||
def show_new_upgrade_page?(%Teams.Team{} = team) do
|
||||
team = Teams.with_subscription(team)
|
||||
feature_flag_enabled? = FunWithFlags.enabled?(:starter_tier, for: team)
|
||||
|
||||
subscription_plan = Plans.get_subscription_plan(team.subscription)
|
||||
|
||||
case {subscription_plan, team.trial_expiry_date} do
|
||||
{%Plan{generation: 5}, _} ->
|
||||
true
|
||||
|
||||
{nil, nil} ->
|
||||
feature_flag_enabled?
|
||||
|
||||
{nil, trial_expiry_date} ->
|
||||
diff = Date.diff(@starter_tier_launch, trial_expiry_date)
|
||||
# Active or recently (less than 10 days ago) ended trials
|
||||
# should be able to subscribe to the plans they saw when
|
||||
# they signed up.
|
||||
if diff <= 10 and diff >= -30 do
|
||||
false
|
||||
else
|
||||
feature_flag_enabled?
|
||||
end
|
||||
|
||||
{_, _} ->
|
||||
feature_flag_enabled?
|
||||
end
|
||||
end
|
||||
|
||||
def grandfathered_team?(nil), do: false
|
||||
|
||||
def grandfathered_team?(team) do
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
defmodule PlausibleWeb.Components.Billing.LegacyPlanBenefits do
|
||||
@moduledoc """
|
||||
[DEPRECATED] This file is essentially a copy of
|
||||
`PlausibleWeb.Components.Billing.PlanBenefits` with the
|
||||
intent of keeping the old behaviour in place for the users without
|
||||
the `starter_tier` feature flag enabled.
|
||||
"""
|
||||
|
||||
use Phoenix.Component
|
||||
alias Plausible.Billing.Plan
|
||||
|
||||
attr :benefits, :list, required: true
|
||||
attr :class, :string, default: nil
|
||||
|
||||
@doc """
|
||||
This function takes a list of benefits returned by either one of:
|
||||
|
||||
* `for_growth/1`
|
||||
* `for_business/2`
|
||||
* `for_enterprise/1`.
|
||||
|
||||
and renders them as HTML.
|
||||
|
||||
The benefits in the given list can be either strings or functions
|
||||
returning a Phoenix component. This allows, for example, to render
|
||||
links within the plan benefit text.
|
||||
"""
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<ul role="list" class={["mt-8 space-y-3 text-sm leading-6 xl:mt-10", @class]}>
|
||||
<li :for={benefit <- @benefits} class="flex gap-x-3">
|
||||
<Heroicons.check class="h-6 w-5 text-indigo-600 dark:text-green-600" />
|
||||
{if is_binary(benefit), do: benefit, else: benefit.(assigns)}
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
This function takes a growth plan and returns a list representing
|
||||
the different benefits a user gets when subscribing to this plan.
|
||||
"""
|
||||
def for_growth(plan) do
|
||||
[
|
||||
team_member_limit_benefit(plan),
|
||||
site_limit_benefit(plan),
|
||||
data_retention_benefit(plan),
|
||||
"Intuitive, fast and privacy-friendly dashboard",
|
||||
"Email/Slack reports",
|
||||
"Google Analytics import"
|
||||
]
|
||||
|> Kernel.++(feature_benefits(plan))
|
||||
|> Kernel.++(["Saved Segments"])
|
||||
|> Enum.filter(& &1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns Business benefits for the given Business plan.
|
||||
|
||||
A second argument is also required - list of Growth benefits. This
|
||||
is because we don't want to list the same benefits in both Growth
|
||||
and Business. Everything in Growth is also included in Business.
|
||||
"""
|
||||
def for_business(plan, growth_benefits) do
|
||||
[
|
||||
"Everything in Growth",
|
||||
team_member_limit_benefit(plan),
|
||||
site_limit_benefit(plan),
|
||||
data_retention_benefit(plan)
|
||||
]
|
||||
|> Kernel.++(feature_benefits(plan))
|
||||
|> Kernel.--(growth_benefits)
|
||||
|> Kernel.++(["Priority support"])
|
||||
|> Enum.filter(& &1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
This function only takes a list of business benefits. Since all
|
||||
limits and features of enterprise plans are configurable, we can
|
||||
say on the upgrade page that enterprise plans include everything
|
||||
in Business.
|
||||
"""
|
||||
def for_enterprise(business_benefits) do
|
||||
team_members =
|
||||
if "Up to 10 team members" in business_benefits, do: "10+ team members"
|
||||
|
||||
data_retention =
|
||||
if "5 years of data retention" in business_benefits, do: "5+ years of data retention"
|
||||
|
||||
[
|
||||
"Everything in Business",
|
||||
team_members,
|
||||
"50+ sites",
|
||||
"600+ Stats API requests per hour",
|
||||
&sites_api_benefit/1,
|
||||
"Single Sign-On (SSO)",
|
||||
data_retention
|
||||
]
|
||||
|> Enum.filter(& &1)
|
||||
end
|
||||
|
||||
defp data_retention_benefit(%Plan{} = plan) do
|
||||
if plan.data_retention_in_years, do: "#{plan.data_retention_in_years} years of data retention"
|
||||
end
|
||||
|
||||
defp team_member_limit_benefit(%Plan{} = plan) do
|
||||
case plan.team_member_limit do
|
||||
:unlimited -> "Unlimited team members"
|
||||
number -> "Up to #{number} team members"
|
||||
end
|
||||
end
|
||||
|
||||
defp site_limit_benefit(%Plan{} = plan), do: "Up to #{plan.site_limit} sites"
|
||||
|
||||
defp feature_benefits(%Plan{} = plan) do
|
||||
Enum.flat_map(plan.features, fn feature_mod ->
|
||||
case feature_mod.name() do
|
||||
:goals -> ["Goals and custom events"]
|
||||
:teams -> []
|
||||
:shared_links -> []
|
||||
:stats_api -> ["Stats API (600 requests per hour)", "Looker Studio Connector"]
|
||||
:revenue_goals -> ["Ecommerce revenue attribution"]
|
||||
_ -> [feature_mod.display_name()]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp sites_api_benefit(assigns) do
|
||||
~H"""
|
||||
<p>
|
||||
Sites API access for
|
||||
<.link
|
||||
class="text-indigo-500 hover:text-indigo-400"
|
||||
href="https://plausible.io/white-label-web-analytics"
|
||||
>
|
||||
reselling
|
||||
</.link>
|
||||
</p>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
|
@ -1,378 +0,0 @@
|
|||
defmodule PlausibleWeb.Components.Billing.LegacyPlanBox do
|
||||
@moduledoc """
|
||||
[DEPRECATED] This file is essentially a copy of
|
||||
`PlausibleWeb.Components.Billing.PlanBox` with the
|
||||
intent of keeping the old behaviour in place for the users without
|
||||
the `starter_tier` feature flag enabled.
|
||||
"""
|
||||
|
||||
use PlausibleWeb, :component
|
||||
|
||||
require Plausible.Billing.Subscription.Status
|
||||
alias PlausibleWeb.Components.Billing.{LegacyPlanBenefits, Notice}
|
||||
alias Plausible.Billing.{Plan, Quota, Subscription}
|
||||
|
||||
def standard(assigns) do
|
||||
highlight =
|
||||
cond do
|
||||
assigns.owned && assigns.recommended -> "Current"
|
||||
assigns.recommended -> "Recommended"
|
||||
true -> nil
|
||||
end
|
||||
|
||||
assigns = assign(assigns, :highlight, highlight)
|
||||
|
||||
~H"""
|
||||
<div
|
||||
id={"#{@kind}-plan-box"}
|
||||
class={[
|
||||
"shadow-lg bg-white rounded-3xl px-6 sm:px-8 py-4 sm:py-6 dark:bg-gray-800",
|
||||
!@highlight && "dark:ring-gray-600",
|
||||
@highlight && "ring-2 ring-indigo-600 dark:ring-indigo-300"
|
||||
]}
|
||||
>
|
||||
<div class="flex items-center justify-between gap-x-4">
|
||||
<h3 class={[
|
||||
"text-lg font-semibold leading-8",
|
||||
!@highlight && "text-gray-900 dark:text-gray-100",
|
||||
@highlight && "text-indigo-600 dark:text-indigo-300"
|
||||
]}>
|
||||
{String.capitalize(to_string(@kind))}
|
||||
</h3>
|
||||
<.pill :if={@highlight} text={@highlight} />
|
||||
</div>
|
||||
<div>
|
||||
<.render_price_info available={@available} {assigns} />
|
||||
<%= if @available do %>
|
||||
<.checkout id={"#{@kind}-checkout"} {assigns} />
|
||||
<% else %>
|
||||
<.contact_button class="bg-indigo-600 hover:bg-indigo-500 text-white" />
|
||||
<% end %>
|
||||
</div>
|
||||
<%= if @owned && @kind == :growth && @plan_to_render.generation < 4 do %>
|
||||
<Notice.growth_grandfathered />
|
||||
<% else %>
|
||||
<LegacyPlanBenefits.render benefits={@benefits} class="text-gray-600 dark:text-gray-100" />
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def enterprise(assigns) do
|
||||
~H"""
|
||||
<div
|
||||
id="enterprise-plan-box"
|
||||
class={[
|
||||
"rounded-3xl px-6 sm:px-8 py-4 sm:py-6 bg-gray-900 shadow-xl dark:bg-gray-800",
|
||||
!@recommended && "dark:ring-gray-600",
|
||||
@recommended && "ring-4 ring-indigo-500 dark:ring-2 dark:ring-indigo-300"
|
||||
]}
|
||||
>
|
||||
<div class="flex items-center justify-between gap-x-4">
|
||||
<h3 class={[
|
||||
"text-lg font-semibold leading-8",
|
||||
!@recommended && "text-white dark:text-gray-100",
|
||||
@recommended && "text-indigo-400 dark:text-indigo-300"
|
||||
]}>
|
||||
Enterprise
|
||||
</h3>
|
||||
<span
|
||||
:if={@recommended}
|
||||
id="enterprise-highlight-pill"
|
||||
class="rounded-full ring-1 ring-indigo-500 px-2.5 py-1 text-xs font-semibold leading-5 text-indigo-400 dark:text-indigo-300 dark:ring-1 dark:ring-indigo-300/50"
|
||||
>
|
||||
Recommended
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-6 flex items-baseline gap-x-1">
|
||||
<span class="text-4xl font-bold tracking-tight text-white dark:text-gray-100">
|
||||
Custom
|
||||
</span>
|
||||
</p>
|
||||
<p class="h-4 mt-1"></p>
|
||||
<.contact_button class="" />
|
||||
<LegacyPlanBenefits.render benefits={@benefits} class="text-gray-300 dark:text-gray-100" />
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp pill(assigns) do
|
||||
~H"""
|
||||
<div class="flex items-center justify-between gap-x-4">
|
||||
<p
|
||||
id="highlight-pill"
|
||||
class="rounded-full bg-indigo-600/10 px-2.5 py-1 text-xs font-semibold leading-5 text-indigo-600 dark:text-indigo-300 dark:ring-1 dark:ring-indigo-300/50"
|
||||
>
|
||||
{@text}
|
||||
</p>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_price_info(%{available: false} = assigns) do
|
||||
~H"""
|
||||
<p id={"#{@kind}-custom-price"} class="mt-6 flex items-baseline gap-x-1">
|
||||
<span class="text-4xl font-bold tracking-tight text-gray-900 dark:text-white">
|
||||
Custom
|
||||
</span>
|
||||
</p>
|
||||
<p class="h-4 mt-1"></p>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_price_info(assigns) do
|
||||
~H"""
|
||||
<p class="mt-6 flex items-baseline gap-x-1">
|
||||
<.price_tag
|
||||
kind={@kind}
|
||||
selected_interval={@selected_interval}
|
||||
plan_to_render={@plan_to_render}
|
||||
/>
|
||||
</p>
|
||||
<p class="mt-1 text-xs">+ VAT if applicable</p>
|
||||
"""
|
||||
end
|
||||
|
||||
defp price_tag(%{plan_to_render: %Plan{monthly_cost: nil}} = assigns) do
|
||||
~H"""
|
||||
<span class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100">
|
||||
N/A
|
||||
</span>
|
||||
"""
|
||||
end
|
||||
|
||||
defp price_tag(%{selected_interval: :monthly} = assigns) do
|
||||
~H"""
|
||||
<span
|
||||
id={"#{@kind}-price-tag-amount"}
|
||||
class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
{@plan_to_render.monthly_cost |> Plausible.Billing.format_price()}
|
||||
</span>
|
||||
<span
|
||||
id={"#{@kind}-price-tag-interval"}
|
||||
class="text-sm font-semibold leading-6 text-gray-600 dark:text-gray-500"
|
||||
>
|
||||
/month
|
||||
</span>
|
||||
"""
|
||||
end
|
||||
|
||||
defp price_tag(%{selected_interval: :yearly} = assigns) do
|
||||
~H"""
|
||||
<span class="text-2xl font-bold w-max tracking-tight line-through text-gray-500 dark:text-gray-600 mr-1">
|
||||
{@plan_to_render.monthly_cost |> Money.mult!(12) |> Plausible.Billing.format_price()}
|
||||
</span>
|
||||
<span
|
||||
id={"#{@kind}-price-tag-amount"}
|
||||
class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
{@plan_to_render.yearly_cost |> Plausible.Billing.format_price()}
|
||||
</span>
|
||||
<span id={"#{@kind}-price-tag-interval"} class="text-sm font-semibold leading-6 text-gray-600">
|
||||
/year
|
||||
</span>
|
||||
"""
|
||||
end
|
||||
|
||||
defp checkout(assigns) do
|
||||
paddle_product_id = get_paddle_product_id(assigns.plan_to_render, assigns.selected_interval)
|
||||
change_plan_link_text = change_plan_link_text(assigns)
|
||||
|
||||
subscription =
|
||||
Plausible.Teams.Billing.get_subscription(assigns.current_team)
|
||||
|
||||
billing_details_expired =
|
||||
Subscription.Status.in?(subscription, [
|
||||
Subscription.Status.paused(),
|
||||
Subscription.Status.past_due()
|
||||
])
|
||||
|
||||
subscription_deleted = Subscription.Status.deleted?(subscription)
|
||||
usage_check = check_usage_within_plan_limits(assigns)
|
||||
|
||||
{checkout_disabled, disabled_message} =
|
||||
cond do
|
||||
not Quota.eligible_for_upgrade?(assigns.usage) ->
|
||||
{true, nil}
|
||||
|
||||
change_plan_link_text == "Currently on this plan" && not subscription_deleted ->
|
||||
{true, nil}
|
||||
|
||||
usage_check != :ok ->
|
||||
{true, "Your usage exceeds this plan"}
|
||||
|
||||
billing_details_expired ->
|
||||
{true, "Please update your billing details first"}
|
||||
|
||||
true ->
|
||||
{false, nil}
|
||||
end
|
||||
|
||||
exceeded_plan_limits =
|
||||
case usage_check do
|
||||
{:error, {:over_plan_limits, limits}} ->
|
||||
limits
|
||||
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
|
||||
feature_usage_check = Quota.ensure_feature_access(assigns.usage, assigns.plan_to_render)
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:paddle_product_id, paddle_product_id)
|
||||
|> assign(:change_plan_link_text, change_plan_link_text)
|
||||
|> assign(:checkout_disabled, checkout_disabled)
|
||||
|> assign(:disabled_message, disabled_message)
|
||||
|> assign(:exceeded_plan_limits, exceeded_plan_limits)
|
||||
|> assign(:confirm_message, losing_features_message(feature_usage_check))
|
||||
|
||||
~H"""
|
||||
<%= if @owned_plan && Plausible.Billing.Subscriptions.resumable?(@current_team.subscription) do %>
|
||||
<.change_plan_link {assigns} />
|
||||
<% else %>
|
||||
<PlausibleWeb.Components.Billing.paddle_button
|
||||
user={@current_user}
|
||||
team={@current_team}
|
||||
{assigns}
|
||||
>
|
||||
Upgrade
|
||||
</PlausibleWeb.Components.Billing.paddle_button>
|
||||
<% end %>
|
||||
<.tooltip :if={@exceeded_plan_limits != [] && @disabled_message}>
|
||||
<div class="pt-2 text-sm w-full flex items-center text-red-700 dark:text-red-500 justify-center">
|
||||
{@disabled_message}
|
||||
<Heroicons.information_circle class="hidden sm:block w-5 h-5 sm:ml-2" />
|
||||
</div>
|
||||
<:tooltip_content>
|
||||
Your usage exceeds the following limit(s):<br /><br />
|
||||
<p :for={limit <- @exceeded_plan_limits}>
|
||||
{Phoenix.Naming.humanize(limit)}<br />
|
||||
</p>
|
||||
</:tooltip_content>
|
||||
</.tooltip>
|
||||
<div
|
||||
:if={@disabled_message && @exceeded_plan_limits == []}
|
||||
class="pt-2 text-sm w-full text-red-700 dark:text-red-500 text-center"
|
||||
>
|
||||
{@disabled_message}
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp check_usage_within_plan_limits(%{available: false}) do
|
||||
{:error, :plan_unavailable}
|
||||
end
|
||||
|
||||
defp check_usage_within_plan_limits(%{
|
||||
available: true,
|
||||
usage: usage,
|
||||
current_team: current_team,
|
||||
plan_to_render: plan
|
||||
}) do
|
||||
# At this point, the user is *not guaranteed* to have a team,
|
||||
# with ongoing trial.
|
||||
trial_active_or_ended_recently? =
|
||||
not is_nil(current_team) and not is_nil(current_team.trial_expiry_date) and
|
||||
Plausible.Teams.trial_days_left(current_team) >= -10
|
||||
|
||||
limit_checking_opts =
|
||||
cond do
|
||||
current_team && current_team.allow_next_upgrade_override ->
|
||||
[ignore_pageview_limit: true]
|
||||
|
||||
trial_active_or_ended_recently? && plan.volume == "10k" ->
|
||||
[pageview_allowance_margin: 0.3]
|
||||
|
||||
trial_active_or_ended_recently? ->
|
||||
[pageview_allowance_margin: 0.15]
|
||||
|
||||
true ->
|
||||
[]
|
||||
end
|
||||
|
||||
Quota.ensure_within_plan_limits(usage, plan, limit_checking_opts)
|
||||
end
|
||||
|
||||
defp get_paddle_product_id(%Plan{monthly_product_id: plan_id}, :monthly), do: plan_id
|
||||
defp get_paddle_product_id(%Plan{yearly_product_id: plan_id}, :yearly), do: plan_id
|
||||
|
||||
defp change_plan_link_text(
|
||||
%{
|
||||
owned_plan: %Plan{kind: from_kind, monthly_pageview_limit: from_volume},
|
||||
plan_to_render: %Plan{kind: to_kind, monthly_pageview_limit: to_volume},
|
||||
current_interval: from_interval,
|
||||
selected_interval: to_interval
|
||||
} = _assigns
|
||||
) do
|
||||
cond do
|
||||
from_kind == :business && to_kind == :growth ->
|
||||
"Downgrade to Growth"
|
||||
|
||||
from_kind == :growth && to_kind == :business ->
|
||||
"Upgrade to Business"
|
||||
|
||||
from_volume == to_volume && from_interval == to_interval ->
|
||||
"Currently on this plan"
|
||||
|
||||
from_volume == to_volume ->
|
||||
"Change billing interval"
|
||||
|
||||
from_volume > to_volume ->
|
||||
"Downgrade"
|
||||
|
||||
true ->
|
||||
"Upgrade"
|
||||
end
|
||||
end
|
||||
|
||||
defp change_plan_link_text(_), do: nil
|
||||
|
||||
defp change_plan_link(assigns) do
|
||||
confirmed =
|
||||
if assigns.confirm_message, do: "confirm(\"#{assigns.confirm_message}\")", else: "true"
|
||||
|
||||
assigns = assign(assigns, :confirmed, confirmed)
|
||||
|
||||
~H"""
|
||||
<button
|
||||
id={"#{@kind}-checkout"}
|
||||
onclick={"if (#{@confirmed}) {window.location = '#{Routes.billing_path(PlausibleWeb.Endpoint, :change_plan_preview, @paddle_product_id)}'}"}
|
||||
class={[
|
||||
"w-full mt-6 block rounded-md py-2 px-3 text-center text-sm font-semibold leading-6 text-white",
|
||||
!@checkout_disabled && "bg-indigo-600 hover:bg-indigo-500",
|
||||
@checkout_disabled && "pointer-events-none bg-gray-400 dark:bg-gray-600"
|
||||
]}
|
||||
>
|
||||
{@change_plan_link_text}
|
||||
</button>
|
||||
"""
|
||||
end
|
||||
|
||||
defp losing_features_message(:ok), do: nil
|
||||
|
||||
defp losing_features_message({:error, {:unavailable_features, features}}) do
|
||||
features_list_str =
|
||||
features
|
||||
|> Enum.map(fn feature_mod -> feature_mod.display_name() end)
|
||||
|> PlausibleWeb.TextHelpers.pretty_join()
|
||||
|
||||
"This plan does not support #{features_list_str}, which you have been using. By subscribing to this plan, you will not have access to #{if length(features) == 1, do: "this feature", else: "these features"}."
|
||||
end
|
||||
|
||||
defp contact_button(assigns) do
|
||||
~H"""
|
||||
<.link
|
||||
href="https://plausible.io/contact"
|
||||
class={[
|
||||
"mt-6 block rounded-md py-2 px-3 text-center text-sm font-semibold leading-6 bg-gray-800 hover:bg-gray-700 text-white dark:bg-indigo-600 dark:hover:bg-indigo-500",
|
||||
@class
|
||||
]}
|
||||
>
|
||||
Contact us
|
||||
</.link>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
|
@ -283,8 +283,7 @@ defmodule PlausibleWeb.Components.Billing.Notice do
|
|||
|
||||
defp lose_grandfathering_warning(%{subscription: subscription} = assigns) do
|
||||
plan = Plans.get_regular_plan(subscription, only_non_expired: true)
|
||||
latest_generation = if FunWithFlags.enabled?(:starter_tier), do: 5, else: 4
|
||||
loses_grandfathering? = plan && plan.generation < latest_generation
|
||||
loses_grandfathering? = plan && plan.generation < 5
|
||||
|
||||
assigns = assign(assigns, :loses_grandfathering?, loses_grandfathering?)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,19 +19,12 @@ defmodule PlausibleWeb.BillingController do
|
|||
def choose_plan(conn, _params) do
|
||||
team = conn.assigns.current_team
|
||||
|
||||
{live_module, hide_header?} =
|
||||
if Plausible.Teams.Billing.show_new_upgrade_page?(team) do
|
||||
{PlausibleWeb.Live.ChoosePlan, true}
|
||||
else
|
||||
{PlausibleWeb.Live.LegacyChoosePlan, false}
|
||||
end
|
||||
|
||||
if Plausible.Teams.Billing.enterprise_configured?(team) do
|
||||
redirect(conn, to: Routes.billing_path(conn, :upgrade_to_enterprise_plan))
|
||||
else
|
||||
render(conn, "choose_plan.html",
|
||||
live_module: live_module,
|
||||
hide_header?: hide_header?,
|
||||
live_module: PlausibleWeb.Live.ChoosePlan,
|
||||
hide_header?: true,
|
||||
disable_global_notices?: true,
|
||||
skip_plausible_tracking: true,
|
||||
connect_live_socket: true
|
||||
|
|
|
|||
|
|
@ -1,321 +0,0 @@
|
|||
defmodule PlausibleWeb.Live.LegacyChoosePlan do
|
||||
@moduledoc """
|
||||
[DEPRECATED] This file is essentially a copy of
|
||||
`PlausibleWeb.Live.ChoosePlan` with the
|
||||
intent of keeping the old behaviour in place for the users without
|
||||
the `starter_tier` feature flag enabled.
|
||||
"""
|
||||
use PlausibleWeb, :live_view
|
||||
|
||||
require Plausible.Billing.Subscription.Status
|
||||
|
||||
alias PlausibleWeb.Components.Billing.{
|
||||
LegacyPlanBox,
|
||||
LegacyPlanBenefits,
|
||||
Notice,
|
||||
PageviewSlider
|
||||
}
|
||||
|
||||
alias Plausible.Billing.{Plans, Quota}
|
||||
|
||||
@contact_link "https://plausible.io/contact"
|
||||
@billing_faq_link "https://plausible.io/docs/billing"
|
||||
|
||||
def mount(_params, %{"remote_ip" => remote_ip}, socket) do
|
||||
socket =
|
||||
socket
|
||||
|> assign_new(:pending_ownership_site_ids, fn %{current_user: current_user} ->
|
||||
Plausible.Teams.Memberships.all_pending_site_transfers(current_user.email)
|
||||
end)
|
||||
|> assign_new(:usage, fn %{
|
||||
current_team: current_team,
|
||||
pending_ownership_site_ids: pending_ownership_site_ids
|
||||
} ->
|
||||
Plausible.Teams.Billing.quota_usage(current_team,
|
||||
with_features: true,
|
||||
pending_ownership_site_ids: pending_ownership_site_ids
|
||||
)
|
||||
end)
|
||||
|> assign_new(:subscription, fn %{current_team: current_team} ->
|
||||
Plausible.Teams.Billing.get_subscription(current_team)
|
||||
end)
|
||||
|> assign_new(:owned_plan, fn %{subscription: subscription} ->
|
||||
Plans.get_regular_plan(subscription, only_non_expired: true)
|
||||
end)
|
||||
|> assign_new(:owned_tier, fn %{owned_plan: owned_plan} ->
|
||||
if owned_plan, do: Map.get(owned_plan, :kind), else: nil
|
||||
end)
|
||||
|> assign_new(:current_interval, fn %{subscription: subscription} ->
|
||||
current_user_subscription_interval(subscription)
|
||||
end)
|
||||
|> assign_new(:available_plans, fn %{subscription: subscription} ->
|
||||
Plans.available_plans_for(subscription,
|
||||
with_prices: true,
|
||||
customer_ip: remote_ip,
|
||||
legacy?: true
|
||||
)
|
||||
end)
|
||||
|> assign_new(:recommended_tier, fn %{
|
||||
usage: usage,
|
||||
available_plans: available_plans,
|
||||
owned_tier: owned_tier
|
||||
} ->
|
||||
highest_growth_plan = List.last(available_plans.growth)
|
||||
highest_business_plan = List.last(available_plans.business)
|
||||
Quota.legacy_suggest_tier(usage, highest_growth_plan, highest_business_plan, owned_tier)
|
||||
end)
|
||||
|> assign_new(:available_volumes, fn %{available_plans: available_plans} ->
|
||||
get_available_volumes(available_plans)
|
||||
end)
|
||||
|> assign_new(:selected_volume, fn %{
|
||||
usage: usage,
|
||||
available_volumes: available_volumes
|
||||
} ->
|
||||
default_selected_volume(usage.monthly_pageviews, available_volumes)
|
||||
end)
|
||||
|> assign_new(:selected_interval, fn %{current_interval: current_interval} ->
|
||||
current_interval || :monthly
|
||||
end)
|
||||
|> assign_new(:selected_growth_plan, fn %{
|
||||
available_plans: available_plans,
|
||||
selected_volume: selected_volume
|
||||
} ->
|
||||
get_plan_by_volume(available_plans.growth, selected_volume)
|
||||
end)
|
||||
|> assign_new(:selected_business_plan, fn %{
|
||||
available_plans: available_plans,
|
||||
selected_volume: selected_volume
|
||||
} ->
|
||||
get_plan_by_volume(available_plans.business, selected_volume)
|
||||
end)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
growth_plan_to_render =
|
||||
assigns.selected_growth_plan || List.last(assigns.available_plans.growth)
|
||||
|
||||
business_plan_to_render =
|
||||
assigns.selected_business_plan || List.last(assigns.available_plans.business)
|
||||
|
||||
growth_benefits =
|
||||
LegacyPlanBenefits.for_growth(growth_plan_to_render)
|
||||
|
||||
business_benefits =
|
||||
LegacyPlanBenefits.for_business(business_plan_to_render, growth_benefits)
|
||||
|
||||
enterprise_benefits = LegacyPlanBenefits.for_enterprise(business_benefits)
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:growth_plan_to_render, growth_plan_to_render)
|
||||
|> assign(:business_plan_to_render, business_plan_to_render)
|
||||
|> assign(:growth_benefits, growth_benefits)
|
||||
|> assign(:business_benefits, business_benefits)
|
||||
|> assign(:enterprise_benefits, enterprise_benefits)
|
||||
|
||||
~H"""
|
||||
<div class="pt-1 pb-12 sm:pb-16 text-gray-900 dark:text-gray-100">
|
||||
<div class="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<Notice.pending_site_ownerships_notice
|
||||
class="pb-6"
|
||||
pending_ownership_count={length(@pending_ownership_site_ids)}
|
||||
/>
|
||||
<Notice.subscription_past_due class="pb-6" subscription={@subscription} />
|
||||
<Notice.subscription_paused class="pb-6" subscription={@subscription} />
|
||||
<Notice.upgrade_ineligible :if={not Quota.eligible_for_upgrade?(@usage)} />
|
||||
<div class="mx-auto max-w-4xl text-center">
|
||||
<p class="text-4xl font-bold tracking-tight lg:text-5xl">
|
||||
{if @owned_plan,
|
||||
do: "Change subscription plan",
|
||||
else: "Upgrade your account"}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-12 flex flex-col gap-8 lg:flex-row items-center lg:items-baseline">
|
||||
<.interval_picker selected_interval={@selected_interval} />
|
||||
<PageviewSlider.render
|
||||
selected_volume={@selected_volume}
|
||||
available_volumes={@available_volumes}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-6 isolate mx-auto grid max-w-md grid-cols-1 gap-8 lg:mx-0 lg:max-w-none lg:grid-cols-3">
|
||||
<LegacyPlanBox.standard
|
||||
kind={:growth}
|
||||
owned={@owned_tier == :growth}
|
||||
recommended={@recommended_tier == :growth}
|
||||
plan_to_render={@growth_plan_to_render}
|
||||
benefits={@growth_benefits}
|
||||
available={!!@selected_growth_plan}
|
||||
{assigns}
|
||||
/>
|
||||
<LegacyPlanBox.standard
|
||||
kind={:business}
|
||||
owned={@owned_tier == :business}
|
||||
recommended={@recommended_tier == :business}
|
||||
plan_to_render={@business_plan_to_render}
|
||||
benefits={@business_benefits}
|
||||
available={!!@selected_business_plan}
|
||||
{assigns}
|
||||
/>
|
||||
<LegacyPlanBox.enterprise
|
||||
benefits={@enterprise_benefits}
|
||||
recommended={@recommended_tier == :custom}
|
||||
/>
|
||||
</div>
|
||||
<p class="mx-auto mt-8 max-w-2xl text-center text-lg leading-8 text-gray-600 dark:text-gray-400">
|
||||
<.render_usage pageview_usage={@usage.monthly_pageviews} />
|
||||
</p>
|
||||
<.pageview_limit_notice :if={!@owned_plan} />
|
||||
<.help_links />
|
||||
</div>
|
||||
</div>
|
||||
<PlausibleWeb.Components.Billing.paddle_script />
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_usage(assigns) do
|
||||
case assigns.pageview_usage do
|
||||
%{last_30_days: _} ->
|
||||
~H"""
|
||||
You have used
|
||||
<b><%= PlausibleWeb.AuthView.delimit_integer(@pageview_usage.last_30_days.total) %></b> billable pageviews in the last 30 days
|
||||
"""
|
||||
|
||||
%{last_cycle: _} ->
|
||||
~H"""
|
||||
You have used
|
||||
<b><%= PlausibleWeb.AuthView.delimit_integer(@pageview_usage.last_cycle.total) %></b> billable pageviews in the last billing cycle
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("set_interval", %{"interval" => interval}, socket) do
|
||||
new_interval =
|
||||
case interval do
|
||||
"yearly" -> :yearly
|
||||
"monthly" -> :monthly
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, selected_interval: new_interval)}
|
||||
end
|
||||
|
||||
def handle_event("slide", %{"slider" => index}, socket) do
|
||||
index = String.to_integer(index)
|
||||
%{available_plans: available_plans, available_volumes: available_volumes} = socket.assigns
|
||||
|
||||
new_volume =
|
||||
if index == length(available_volumes) do
|
||||
:enterprise
|
||||
else
|
||||
Enum.at(available_volumes, index)
|
||||
end
|
||||
|
||||
{:noreply,
|
||||
assign(socket,
|
||||
selected_volume: new_volume,
|
||||
selected_growth_plan: get_plan_by_volume(available_plans.growth, new_volume),
|
||||
selected_business_plan: get_plan_by_volume(available_plans.business, new_volume)
|
||||
)}
|
||||
end
|
||||
|
||||
defp default_selected_volume(pageview_usage, available_volumes) do
|
||||
total =
|
||||
case pageview_usage do
|
||||
%{last_30_days: usage} -> usage.total
|
||||
%{last_cycle: usage} -> usage.total
|
||||
end
|
||||
|
||||
Enum.find(available_volumes, &(total < &1)) || :enterprise
|
||||
end
|
||||
|
||||
defp current_user_subscription_interval(subscription) do
|
||||
case Plans.subscription_interval(subscription) do
|
||||
"yearly" -> :yearly
|
||||
"monthly" -> :monthly
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp get_plan_by_volume(_, :enterprise), do: nil
|
||||
|
||||
defp get_plan_by_volume(plans, volume) do
|
||||
Enum.find(plans, &(&1.monthly_pageview_limit == volume))
|
||||
end
|
||||
|
||||
defp interval_picker(assigns) do
|
||||
~H"""
|
||||
<div class="lg:flex-1 lg:order-3 lg:justify-end flex">
|
||||
<div class="relative">
|
||||
<.two_months_free />
|
||||
<fieldset class="grid grid-cols-2 gap-x-1 rounded-full bg-white dark:bg-gray-700 p-1 text-center text-sm font-semibold leading-5 shadow dark:ring-gray-600">
|
||||
<label
|
||||
class={"cursor-pointer rounded-full px-2.5 py-1 text-gray-900 dark:text-white #{if @selected_interval == :monthly, do: "bg-indigo-600 text-white"}"}
|
||||
phx-click="set_interval"
|
||||
phx-value-interval="monthly"
|
||||
>
|
||||
<input type="radio" name="frequency" value="monthly" class="sr-only" />
|
||||
<span>Monthly</span>
|
||||
</label>
|
||||
<label
|
||||
class={"cursor-pointer rounded-full px-2.5 py-1 text-gray-900 dark:text-white #{if @selected_interval == :yearly, do: "bg-indigo-600 text-white"}"}
|
||||
phx-click="set_interval"
|
||||
phx-value-interval="yearly"
|
||||
>
|
||||
<input type="radio" name="frequency" value="yearly" class="sr-only" />
|
||||
<span>Yearly</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def two_months_free(assigns) do
|
||||
~H"""
|
||||
<span class="absolute -right-5 -top-4 whitespace-no-wrap w-max px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-yellow-100 border border-yellow-300 text-yellow-700">
|
||||
2 months free
|
||||
</span>
|
||||
"""
|
||||
end
|
||||
|
||||
defp pageview_limit_notice(assigns) do
|
||||
~H"""
|
||||
<div class="mt-12 mx-auto mt-6 max-w-2xl">
|
||||
<dt>
|
||||
<p class="w-full text-center text-gray-900 dark:text-gray-100">
|
||||
<span class="text-center font-semibold leading-7">
|
||||
What happens if I go over my page views limit?
|
||||
</span>
|
||||
</p>
|
||||
</dt>
|
||||
<dd class="mt-3">
|
||||
<div class="text-justify leading-7 block text-gray-600 dark:text-gray-100">
|
||||
You will never be charged extra for an occasional traffic spike. There are no surprise fees and your card will never be charged unexpectedly. If your page views exceed your plan for two consecutive months, we will contact you to upgrade to a higher plan for the following month. You will have two weeks to make a decision. You can decide to continue with a higher plan or to cancel your account at that point.
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp help_links(assigns) do
|
||||
~H"""
|
||||
<div class="mt-8 text-center">
|
||||
Questions? <a class="text-indigo-600" href={contact_link()}>Contact us</a>
|
||||
or see <a class="text-indigo-600" href={billing_faq_link()}>billing FAQ</a>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp get_available_volumes(%{business: business_plans, growth: growth_plans}) do
|
||||
growth_volumes = Enum.map(growth_plans, & &1.monthly_pageview_limit)
|
||||
business_volumes = Enum.map(business_plans, & &1.monthly_pageview_limit)
|
||||
|
||||
(growth_volumes ++ business_volumes)
|
||||
|> Enum.uniq()
|
||||
end
|
||||
|
||||
defp contact_link(), do: @contact_link
|
||||
|
||||
defp billing_faq_link(), do: @billing_faq_link
|
||||
end
|
||||
|
|
@ -13,8 +13,6 @@ use Plausible
|
|||
|
||||
import Plausible.Teams.Test
|
||||
|
||||
FunWithFlags.enable(:starter_tier)
|
||||
|
||||
words =
|
||||
for i <- 0..(:erlang.system_info(:atom_count) - 1),
|
||||
do: :erlang.binary_to_term(<<131, 75, i::24>>)
|
||||
|
|
|
|||
|
|
@ -622,77 +622,4 @@ defmodule Plausible.BillingTest do
|
|||
refute Plausible.Teams.Billing.has_active_subscription?(paused_team)
|
||||
refute Plausible.Teams.Billing.has_active_subscription?(nil)
|
||||
end
|
||||
|
||||
@v4_plan_id "857088"
|
||||
@v5_plan_id "910429"
|
||||
|
||||
describe "show_new_upgrade_page?/1" do
|
||||
@describetag :ee_only
|
||||
|
||||
test "returns true for non-existing team" do
|
||||
assert Plausible.Teams.Billing.show_new_upgrade_page?(nil) == true
|
||||
end
|
||||
|
||||
test "returns true for team with no trial_expiry_date" do
|
||||
{:ok, team} = new_user() |> Plausible.Teams.get_or_create()
|
||||
team = %{team | trial_expiry_date: nil}
|
||||
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == true
|
||||
end
|
||||
|
||||
test "returns true for a user already on a v5 plan regardless of starter_tier feature flag" do
|
||||
team =
|
||||
new_user()
|
||||
|> subscribe_to_plan(@v5_plan_id)
|
||||
|> team_of()
|
||||
|
||||
FunWithFlags.disable(:starter_tier, for_actor: team)
|
||||
|
||||
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == true
|
||||
end
|
||||
|
||||
test "returns false for a trial that expired 10 days before starter tier release" do
|
||||
trial_expiry_date =
|
||||
Plausible.Teams.Billing.starter_tier_launch()
|
||||
|> Date.shift(day: -10)
|
||||
|
||||
team = new_user(trial_expiry_date: trial_expiry_date) |> team_of()
|
||||
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == false
|
||||
end
|
||||
|
||||
test "returns true for a trial that expired 11 days before starter tier release" do
|
||||
trial_expiry_date =
|
||||
Plausible.Teams.Billing.starter_tier_launch()
|
||||
|> Date.shift(day: -11)
|
||||
|
||||
team = new_user(trial_expiry_date: trial_expiry_date) |> team_of()
|
||||
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == true
|
||||
end
|
||||
|
||||
test "returns false for a trial that started on the day of starter tier release" do
|
||||
trial_expiry_date =
|
||||
Plausible.Teams.Billing.starter_tier_launch()
|
||||
|> Date.shift(day: 30)
|
||||
|
||||
team = new_user(trial_expiry_date: trial_expiry_date) |> team_of()
|
||||
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == false
|
||||
end
|
||||
|
||||
test "returns true for a trial that started after starter tier release" do
|
||||
trial_expiry_date =
|
||||
Plausible.Teams.Billing.starter_tier_launch()
|
||||
|> Date.shift(day: 31)
|
||||
|
||||
team = new_user(trial_expiry_date: trial_expiry_date) |> team_of()
|
||||
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == true
|
||||
end
|
||||
|
||||
test "returns true for a v4 subscription" do
|
||||
team =
|
||||
new_user(trial_expiry_date: ~D[2025-01-01])
|
||||
|> subscribe_to_plan(@v4_plan_id)
|
||||
|> team_of()
|
||||
|
||||
assert Plausible.Teams.Billing.show_new_upgrade_page?(team) == true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -8,7 +8,6 @@ Application.ensure_all_started(:double)
|
|||
|
||||
FunWithFlags.enable(:channels)
|
||||
FunWithFlags.enable(:scroll_depth)
|
||||
FunWithFlags.enable(:starter_tier)
|
||||
|
||||
Ecto.Adapters.SQL.Sandbox.mode(Plausible.Repo, :manual)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue