analytics/lib/plausible_web/components/billing/plan_box.ex
RobertJoonas 22ecbe7bc7
Refactor: Split up the choose_plan LV code (#3637)
* move format_price to Plausible.Billing

* move PlausibleWeb.Components.Billing file to subfolder

* extract new Notice module

* rename test file and module name

* move growth_grandfathered notice to notice.ex

* extract a PlanBenefits module

* extract PlanBox component

* extract PageviewSlider component

* fix plan benefits text color
2023-12-15 13:59:16 -03:00

294 lines
9.1 KiB
Elixir

defmodule PlausibleWeb.Components.Billing.PlanBox do
@moduledoc false
use Phoenix.Component
require Plausible.Billing.Subscription.Status
alias PlausibleWeb.Components.Billing.{PlanBenefits, Notice}
alias Plausible.Billing.{Plan, Quota, Subscription}
alias PlausibleWeb.Router.Helpers, as: Routes
def standard(assigns) do
highlight =
cond do
assigns.owned -> "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 %>
<PlanBenefits.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 dark:ring-gray-600"
>
<h3 class="text-lg font-semibold leading-8 text-white dark:text-gray-100">Enterprise</h3>
<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="" />
<PlanBenefits.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)
usage_within_limits =
Quota.ensure_can_subscribe_to_plan(assigns.user, assigns.plan_to_render, assigns.usage) ==
:ok
subscription = assigns.user.subscription
billing_details_expired =
Subscription.Status.in?(subscription, [
Subscription.Status.paused(),
Subscription.Status.past_due()
])
subscription_deleted = Subscription.Status.deleted?(subscription)
{checkout_disabled, disabled_message} =
cond do
assigns.usage.sites == 0 ->
{true, nil}
change_plan_link_text == "Currently on this plan" && not subscription_deleted ->
{true, nil}
assigns.available && !usage_within_limits ->
{true, "Your usage exceeds this plan"}
billing_details_expired ->
{true, "Please update your billing details first"}
true ->
{false, nil}
end
features_to_lose = assigns.usage.features -- assigns.plan_to_render.features
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(:confirm_message, losing_features_message(features_to_lose))
~H"""
<%= if @owned_plan && Plausible.Billing.Subscriptions.resumable?(@user.subscription) do %>
<.change_plan_link {assigns} />
<% else %>
<PlausibleWeb.Components.Billing.paddle_button {assigns}>
Upgrade
</PlausibleWeb.Components.Billing.paddle_button>
<% end %>
<p :if={@disabled_message} class="h-0 text-center text-sm text-red-700 dark:text-red-500">
<%= @disabled_message %>
</p>
"""
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([]), do: nil
defp losing_features_message(features_to_lose) do
features_list_str =
features_to_lose
|> Enum.map(& &1.display_name)
|> PlausibleWeb.TextHelpers.pretty_join()
"This plan does not support #{features_list_str}, which you are currently using. Please note that by subscribing to this plan you will lose access to #{if length(features_to_lose) == 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