diff --git a/lib/plausible_web/components/billing/plan_box.ex b/lib/plausible_web/components/billing/plan_box.ex
index 37e21b407645..69514703f3aa 100644
--- a/lib/plausible_web/components/billing/plan_box.ex
+++ b/lib/plausible_web/components/billing/plan_box.ex
@@ -39,7 +39,9 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
<.render_price_info available={@available} {assigns} />
<%= if @available do %>
- <.checkout id={"#{@kind}-checkout"} {assigns} />
+
+ <.checkout id={"#{@kind}-checkout"} {assigns} />
+
<% else %>
<.contact_button class="bg-indigo-600 hover:bg-indigo-500 text-white" />
<% end %>
@@ -164,33 +166,36 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
])
subscription_deleted = Subscription.Status.deleted?(subscription)
+ usage_within_limits = usage_within_plan_limits?(assigns)
- {checkout_disabled, disabled_message} =
+ {checkout_disabled, disabled_message, limits} =
cond do
not assigns.eligible_for_upgrade? ->
- {true, nil}
+ {true, nil, nil}
change_plan_link_text == "Currently on this plan" && not subscription_deleted ->
- {true, nil}
+ {true, nil, nil}
- assigns.available && !usage_within_plan_limits?(assigns) ->
- {true, "Your usage exceeds this plan"}
+ usage_within_limits != :ok ->
+ {true, "Your usage exceeds this plan", limits(usage_within_limits)}
billing_details_expired ->
- {true, "Please update your billing details first"}
+ {true, "Please update your billing details first", nil}
true ->
- {false, nil}
+ {false, nil, nil}
end
features_to_lose = assigns.usage.features -- assigns.plan_to_render.features
assigns =
assigns
+ |> assign(:display_tooltip_product_id, assigns[:display_tooltip_product_id])
|> 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(:disabled_message_limits, limits)
|> assign(:confirm_message, losing_features_message(features_to_lose))
~H"""
@@ -201,13 +206,75 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
Upgrade
<% end %>
-
- <%= @disabled_message %>
+
+ <%= if @disabled_message_limits do %>
+ <.disabled_with_tooltip
+ show_tooltip?={@paddle_product_id == @display_tooltip_product_id}
+ paddle_product_id={@paddle_product_id}
+ disabled_message={@disabled_message}
+ limits={@disabled_message_limits}
+ />
+ <% else %>
+ <%= @disabled_message %>
+ <% end %>
"""
end
- defp usage_within_plan_limits?(%{usage: usage, user: user, plan_to_render: plan}) do
+ attr :show_tooltip?, :boolean, default: false
+ attr :paddle_product_id, :string, required: true
+ attr :limits, :list, default: nil
+ attr :disabled_message, :string, required: true
+
+ def disabled_with_tooltip(assigns) do
+ opacity =
+ if assigns.show_tooltip? do
+ "opacity-90"
+ else
+ "opacity-0 group-hover:opacity-90"
+ end
+
+ assigns = assign(assigns, opacity: opacity)
+
+ ~H"""
+
+
+ Your usage exceeds the following limit(s):
+ <%= for limit <- @limits do %>
+ <%= Phoenix.Naming.humanize(limit) %>
+ <% end %>
+
+
+
+ <%= @disabled_message %>
+
+
+
+ """
+ end
+
+ defp usage_within_plan_limits?(%{available: false}) do
+ {:error, :plan_unavailable}
+ end
+
+ defp usage_within_plan_limits?(%{
+ available: true,
+ usage: usage,
+ user: user,
+ plan_to_render: plan
+ }) do
# At this point, the user is *not guaranteed* to have a `trial_expiry_date`,
# because in the past we've let users upgrade without that constraint, as
# well as transfer sites to those accounts. to these accounts we won't be
@@ -232,9 +299,15 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
[]
end
- Quota.ensure_within_plan_limits(usage, plan, limit_checking_opts) == :ok
+ Quota.ensure_within_plan_limits(usage, plan, limit_checking_opts)
end
+ defp limits({:error, {:over_plan_limits, limits}}) do
+ limits
+ end
+
+ defp limits(_), do: nil
+
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
diff --git a/lib/plausible_web/live/choose_plan.ex b/lib/plausible_web/live/choose_plan.ex
index 39d2866afead..671a3e32a715 100644
--- a/lib/plausible_web/live/choose_plan.ex
+++ b/lib/plausible_web/live/choose_plan.ex
@@ -189,6 +189,14 @@ defmodule PlausibleWeb.Live.ChoosePlan do
)}
end
+ def handle_event("show-tooltip", %{"paddle-product-id" => product_id}, socket) do
+ {:noreply, assign(socket, display_tooltip_product_id: product_id)}
+ end
+
+ def handle_event("hide-tooltip", _, socket) do
+ {:noreply, assign(socket, display_tooltip_product_id: nil)}
+ end
+
defp default_selected_volume(%Plan{monthly_pageview_limit: limit}, _, _), do: limit
defp default_selected_volume(_, last_30_days_usage, available_volumes) do
diff --git a/test/plausible_web/live/choose_plan_test.exs b/test/plausible_web/live/choose_plan_test.exs
index a463cdbdcab1..7df389c1ac70 100644
--- a/test/plausible_web/live/choose_plan_test.exs
+++ b/test/plausible_web/live/choose_plan_test.exs
@@ -19,6 +19,8 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
@slider_value "#slider-value"
@growth_plan_box "#growth-plan-box"
+ @growth_plan_tooltip "#growth-plan-box .tooltip-wrapper span"
+ @growth_plan_tooltip_link ~s/#growth-plan-box .tooltip-wrapper a[phx-click="show-tooltip"]/
@growth_price_tag_amount "#growth-price-tag-amount"
@growth_price_tag_interval "#growth-price-tag-interval"
@growth_highlight_pill "#{@growth_plan_box} #highlight-pill"
@@ -243,6 +245,7 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
refute class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none"
refute class_of_element(doc, @business_checkout_button) =~ "pointer-events-none"
+ refute element_exists?(doc, @growth_plan_tooltip)
generate_usage_for(site, 1)
@@ -251,6 +254,9 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none"
assert class_of_element(doc, @business_checkout_button) =~ "pointer-events-none"
+
+ assert text_of_element(doc, @growth_plan_tooltip) ==
+ "Your usage exceeds the following limit(s): Monthly pageview limit"
end
test "allows upgrade to a 10k plan with a pageview allowance margin of 0.3 when trial ended 10 days ago",
@@ -497,6 +503,9 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
assert text_of_element(doc, @growth_plan_box) =~ "Your usage exceeds this plan"
assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none"
+
+ assert text_of_element(doc, @growth_plan_tooltip) ==
+ "Your usage exceeds the following limit(s): Team member limit"
end
test "checkout is disabled when sites usage exceeds rendered plan limit", %{
@@ -509,6 +518,55 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
assert text_of_element(doc, @growth_plan_box) =~ "Your usage exceeds this plan"
assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none"
+
+ assert text_of_element(doc, @growth_plan_tooltip) ==
+ "Your usage exceeds the following limit(s): Site limit"
+ end
+
+ test "clicking the tooltip changes makes it sticky", %{
+ conn: conn,
+ user: user
+ } do
+ insert(:site,
+ memberships: [
+ build(:site_membership, user: user, role: :owner),
+ build(:site_membership, user: build(:user)),
+ build(:site_membership, user: build(:user)),
+ build(:site_membership, user: build(:user)),
+ build(:site_membership, user: build(:user))
+ ]
+ )
+
+ {:ok, lv, doc} = get_liveview(conn)
+
+ assert class_of_element(doc, @growth_plan_tooltip) =~ "opacity-0 group-hover:opacity-90"
+
+ doc = element(lv, @growth_plan_tooltip_link) |> render_click()
+
+ assert class_of_element(doc, @growth_plan_tooltip) =~ "opacity-90"
+ refute class_of_element(doc, @growth_plan_tooltip) =~ "opacity-0"
+ end
+
+ test "when more than one limit is exceeded, the tooltip enumerates them", %{
+ conn: conn,
+ user: user
+ } do
+ for _ <- 1..11, do: insert(:site, members: [user])
+
+ insert(:site,
+ memberships: [
+ build(:site_membership, user: user, role: :owner),
+ build(:site_membership, user: build(:user)),
+ build(:site_membership, user: build(:user)),
+ build(:site_membership, user: build(:user)),
+ build(:site_membership, user: build(:user))
+ ]
+ )
+
+ {:ok, _lv, doc} = get_liveview(conn)
+
+ assert text_of_element(doc, @growth_plan_tooltip) =~ "Team member limit"
+ assert text_of_element(doc, @growth_plan_tooltip) =~ "Site limit"
end
test "checkout is not disabled when pageview usage exceeded but next upgrade allowed by override",