Notice across the app about Funnels and Revenue goals private preview end (#3510)

* change upgrade CTA notice message for enterprise and business plans

* add dismissable option to Generic.notice

* more general notice about losing premium features in X days

* save notice dismissed per user

... more than one Plausible account can use the same device, so we should
scope the fact that the notice has been dismissed by user id.

* fix bug applying classes to Generic.notice

* apply shadow to the new notice on light mode

* use Heroicons.x_mark instead of raw SVG

* use Enum.filter instead of list comprehension
This commit is contained in:
RobertJoonas 2023-11-13 16:19:58 +00:00 committed by GitHub
parent 0c2e6b8751
commit fdf1462c04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 106 additions and 53 deletions

View File

@ -4,9 +4,10 @@ defmodule PlausibleWeb.Components.Billing do
use Phoenix.Component
import PlausibleWeb.Components.Generic
require Plausible.Billing.Subscription.Status
alias Plausible.Billing.Feature.{RevenueGoals, Funnels}
alias Plausible.Billing.Feature.{Props, StatsAPI}
alias PlausibleWeb.Router.Helpers, as: Routes
alias Plausible.Billing.{Subscription, Plans, Subscriptions}
alias Plausible.Billing.{Subscription, Plans, Plan, Subscriptions}
attr(:billable_user, Plausible.Auth.User, required: true)
attr(:current_user, Plausible.Auth.User, required: true)
@ -17,15 +18,10 @@ defmodule PlausibleWeb.Components.Billing do
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
def premium_feature_notice(assigns) do
billable_user = Plausible.Users.with_subscription(assigns.billable_user)
plan = Plans.get_regular_plan(billable_user.subscription, only_non_expired: true)
business? = plan && plan.kind == :business
legacy_feature_access? =
Timex.before?(assigns.billable_user.inserted_at, Plans.business_tier_launch()) &&
assigns.feature_mod in [StatsAPI, Props]
private_preview? = FunWithFlags.enabled?(:premium_features_private_preview)
has_access? = assigns.feature_mod.check_availability(assigns.billable_user) == :ok
cond do
@ -39,14 +35,6 @@ defmodule PlausibleWeb.Components.Billing do
</.notice>
"""
private_preview? && !business? ->
~H"""
<.notice class="rounded-t-md rounded-b-none" size={@size} {@rest}>
Business plans are now live! The private preview of <%= @feature_mod.display_name() %> ends <%= private_preview_days_remaining() %>. If you wish to continue using this feature,
<.upgrade_call_to_action current_user={@current_user} billable_user={@billable_user} />.
</.notice>
"""
not has_access? ->
~H"""
<.notice class="rounded-t-md rounded-b-none" size={@size} {@rest}>
@ -60,7 +48,7 @@ defmodule PlausibleWeb.Components.Billing do
end
end
defp private_preview_days_remaining do
defp private_preview_end do
private_preview_ends_at = Timex.shift(Plausible.Billing.Plans.business_tier_launch(), days: 8)
days_remaining = Timex.diff(private_preview_ends_at, NaiveDateTime.utc_now(), :day)
@ -113,7 +101,7 @@ defmodule PlausibleWeb.Components.Billing do
"""
true ->
~H"please contact support@plausible.io about the Enterprise plan"
~H"please contact hello@plausible.io to upgrade your subscription"
end
end
@ -317,6 +305,48 @@ defmodule PlausibleWeb.Components.Billing do
def subscription_paused_notice(assigns), do: ~H""
def private_preview_end_notice(assigns) do
user = assigns.user |> Plausible.Users.with_subscription()
features_to_lose =
case Plans.get_subscription_plan(user.subscription) do
nil ->
[]
%Plan{kind: :business} ->
[]
_free_10k_or_enterprise_or_growth ->
used_features = Plausible.Billing.Quota.features_usage(assigns.user)
Enum.filter([Funnels, RevenueGoals], &(&1 in used_features))
end
assigns = assign(assigns, :features_to_lose, features_to_lose)
~H"""
<div
:if={FunWithFlags.enabled?(:premium_features_private_preview) && @features_to_lose != []}
class="container mt-2"
>
<.notice
class="shadow-md dark:shadow-none"
dismissable_id={"premium_features_private_preview_end__#{@user.id}"}
>
Business plans are now live! The private preview of <%= PlausibleWeb.TextHelpers.pretty_join(
Enum.map(@features_to_lose, & &1.display_name())
) %> ends <b><%= private_preview_end() %></b>. If you wish to continue using <%= if length(
@features_to_lose
) == 1,
do:
"this feature",
else:
"these features" %>,
<.upgrade_call_to_action current_user={@user} billable_user={@user} />.
</.notice>
</div>
"""
end
def present_enterprise_plan(assigns) do
~H"""
<ul class="w-full py-4">

View File

@ -60,42 +60,61 @@ defmodule PlausibleWeb.Components.Generic do
attr(:title, :string, default: "Notice")
attr(:size, :atom, default: :sm)
attr(:dismissable_id, :any, default: nil)
attr(:class, :string, default: "")
attr(:rest, :global)
slot(:inner_block)
def notice(assigns) do
~H"""
<div class="rounded-md bg-yellow-50 dark:bg-yellow-100 p-4" {@rest}>
<div class="flex">
<div :if={@size !== :xs} class="flex-shrink-0">
<svg
class="h-5 w-5 text-yellow-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class="ml-3">
<h3
:if={@size !== :xs}
class={"text-#{@size} font-medium text-yellow-800 dark:text-yellow-900 mb-2"}
>
<%= @title %>
</h3>
<div class={"text-#{@size} text-yellow-700 dark:text-yellow-800"}>
<p>
<%= render_slot(@inner_block) %>
</p>
<div id={@dismissable_id} class={@dismissable_id && "hidden"}>
<div class={"rounded-md bg-yellow-50 dark:bg-yellow-100 p-4 relative #{@class}"} {@rest}>
<button
:if={@dismissable_id}
class="absolute right-0 top-0 m-2 text-yellow-800 dark:text-yellow-900"
onclick={"localStorage['notice_dismissed__#{@dismissable_id}'] = 'true'; document.getElementById('#{@dismissable_id}').classList.add('hidden')"}
>
<Heroicons.x_mark class="h-4 w-4 hover:stroke-2" />
</button>
<div class="flex">
<div :if={@size !== :xs} class="flex-shrink-0">
<svg
class="h-5 w-5 text-yellow-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class="ml-3">
<h3
:if={@size !== :xs}
class={"text-#{@size} font-medium text-yellow-800 dark:text-yellow-900 mb-2"}
>
<%= @title %>
</h3>
<div class={"text-#{@size} text-yellow-700 dark:text-yellow-800"}>
<p>
<%= render_slot(@inner_block) %>
</p>
</div>
</div>
</div>
</div>
</div>
<script data-key={@dismissable_id}>
const dismissId = document.currentScript.dataset.key
const localStorageKey = `notice_dismissed__${dismissId}`
if (localStorage[localStorageKey] !== 'true') {
document.getElementById(dismissId).classList.remove('hidden')
}
</script>
"""
end

View File

@ -95,12 +95,16 @@
</div>
<% end %>
<.subscription_past_due_notice
subscription={@conn.assigns[:current_user] && @conn.assigns[:current_user].subscription}
class="container"
/>
<%= if @conn.assigns[:current_user] do %>
<.subscription_past_due_notice
subscription={@conn.assigns.current_user.subscription}
class="container"
/>
<.subscription_paused_notice
subscription={@conn.assigns[:current_user] && @conn.assigns[:current_user].subscription}
class="container"
/>
<.subscription_paused_notice
subscription={@conn.assigns.current_user.subscription}
class="container"
/>
<.private_preview_end_notice user={@conn.assigns.current_user} />
<% end %>

View File

@ -120,7 +120,7 @@ defmodule PlausibleWeb.Components.BillingTest do
)
assert rendered =~ "Your account is limited to 10 users."
assert rendered =~ "please contact support@plausible.io about the Enterprise plan"
assert rendered =~ "please contact hello@plausible.io to upgrade your subscription"
end
test "limit_exceeded_notice/1 when billable user is on a business plan displays support email" do
@ -135,6 +135,6 @@ defmodule PlausibleWeb.Components.BillingTest do
)
assert rendered =~ "Your account is limited to 10 users."
assert rendered =~ "please contact support@plausible.io about the Enterprise plan"
assert rendered =~ "please contact hello@plausible.io to upgrade your subscription"
end
end