2023-10-03 13:36:22 +03:00
defmodule PlausibleWeb.Live.ChoosePlan do
@moduledoc """
LiveView for upgrading to a plan , or changing an existing plan .
"""
use Phoenix.LiveView
use Phoenix.HTML
import PlausibleWeb.Components.Billing
2023-10-10 20:35:17 +03:00
require Plausible.Billing.Subscription.Status
alias Plausible.Users
alias Plausible.Billing . { Plans , Plan , Quota , Subscription }
alias PlausibleWeb.Router.Helpers , as : Routes
2023-10-03 13:36:22 +03:00
@contact_link " https://plausible.io/contact "
@billing_faq_link " https://plausible.io/docs/billing "
def mount ( _params , %{ " user_id " = > user_id } , socket ) do
socket =
socket
|> assign_new ( :user , fn ->
Users . with_subscription ( user_id )
end )
|> assign_new ( :usage , fn %{ user : user } ->
2023-10-26 18:20:38 +03:00
Quota . usage ( user , with_features : true )
2023-10-03 13:36:22 +03:00
end )
|> assign_new ( :owned_plan , fn %{ user : %{ subscription : subscription } } ->
Plans . get_regular_plan ( subscription , only_non_expired : true )
end )
2023-11-02 17:46:14 +03:00
|> assign_new ( :owned_tier , fn %{ owned_plan : owned_plan } ->
if owned_plan , do : Map . get ( owned_plan , :kind ) , else : nil
end )
|> assign_new ( :recommended_tier , fn %{ owned_plan : owned_plan , user : user } ->
if owned_plan , do : nil , else : Plans . suggest_tier ( user )
end )
2023-10-03 13:36:22 +03:00
|> assign_new ( :current_interval , fn %{ user : user } ->
current_user_subscription_interval ( user . subscription )
end )
|> assign_new ( :available_plans , fn %{ user : user } ->
2023-11-02 17:46:14 +03:00
Plans . available_plans_for ( user , with_prices : true )
2023-10-03 13:36:22 +03:00
end )
|> assign_new ( :available_volumes , fn %{ available_plans : available_plans } ->
get_available_volumes ( available_plans )
end )
|> assign_new ( :selected_volume , fn %{
owned_plan : owned_plan ,
usage : usage ,
available_volumes : available_volumes
} ->
2023-10-26 18:20:38 +03:00
default_selected_volume ( owned_plan , usage . monthly_pageviews , available_volumes )
2023-10-03 13:36:22 +03:00
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
2023-10-23 19:42:00 +03:00
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 = growth_benefits ( growth_plan_to_render )
business_benefits = business_benefits ( business_plan_to_render , growth_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 ( business_benefits ) )
2023-10-03 13:36:22 +03:00
~H """
< div class = " bg-gray-100 dark:bg-gray-900 pt-1 pb-12 sm:pb-16 text-gray-900 dark:text-gray-100 " >
2023-10-17 13:36:25 +03:00
< div class = " mx-auto max-w-7xl px-6 lg:px-20 " >
2023-10-03 13:36:22 +03:00
< . subscription_past_due_notice class = " pb-2 " subscription = { @user . subscription } / >
< . subscription_paused_notice class = " pb-2 " subscription = { @user . subscription } / >
< div class = " mx-auto max-w-4xl text-center " >
2023-10-17 13:36:25 +03:00
< p class = " text-4xl font-bold tracking-tight lg:text-5xl " >
2023-10-03 13:36:22 +03:00
< % = if @owned_plan ,
do : " Change subscription plan " ,
else : " Upgrade your account " % >
< / p >
< / div >
2023-10-17 13:36:25 +03:00
< div class = " mt-12 max-w-md lg:max-w-none mx-auto flex flex-col lg:flex-row-reverse justify-between " >
< . interval_picker selected_interval = { @selected_interval } / >
< . slider selected_volume = { @selected_volume } available_volumes = { @available_volumes } / >
< / div >
2023-10-03 13:36:22 +03:00
< 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 " >
< . plan_box
kind = { :growth }
2023-11-02 17:46:14 +03:00
owned = { @owned_tier == :growth }
recommended = { @recommended_tier == :growth }
2023-10-23 19:42:00 +03:00
plan_to_render = { @growth_plan_to_render }
benefits = { @growth_benefits }
2023-10-03 13:36:22 +03:00
available = { ! ! @selected_growth_plan }
{ assigns }
/ >
< . plan_box
kind = { :business }
2023-11-02 17:46:14 +03:00
owned = { @owned_tier == :business }
recommended = { @recommended_tier == :business }
2023-10-23 19:42:00 +03:00
plan_to_render = { @business_plan_to_render }
benefits = { @business_benefits }
2023-10-03 13:36:22 +03:00
available = { ! ! @selected_business_plan }
{ assigns }
/ >
2023-10-23 19:42:00 +03:00
< . enterprise_plan_box benefits = { @enterprise_benefits } / >
2023-10-03 13:36:22 +03:00
< / div >
2023-10-17 13:36:25 +03:00
< p class = " mx-auto mt-8 max-w-2xl text-center text-lg leading-8 text-gray-600 dark:text-gray-400 " >
2023-10-26 18:20:38 +03:00
You have used < b > < % = PlausibleWeb.AuthView . delimit_integer ( @usage . monthly_pageviews ) % > < / b >
billable pageviews in the last 30 days
2023-10-03 13:36:22 +03:00
< / p >
< . pageview_limit_notice :if = { ! @owned_plan } / >
< . help_links / >
< / div >
< / div >
< . slider_styles / >
< . paddle_script / >
"""
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 ( % Plan { monthly_pageview_limit : limit } , _ , _ ) , do : limit
2023-10-26 18:20:38 +03:00
defp default_selected_volume ( _ , pageview_usage , available_volumes ) do
Enum . find ( available_volumes , & ( pageview_usage < &1 ) ) || :enterprise
2023-10-03 13:36:22 +03:00
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 """
2023-10-17 13:36:25 +03:00
< div class = " mt-4 lg:flex justify-center self-start lg:self-end " >
< 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 " >
2023-10-03 13:36:22 +03:00
< label
2023-10-17 13:36:25 +03:00
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 " } " }
2023-10-03 13:36:22 +03:00
phx - click = " set_interval "
phx - value - interval = " monthly "
>
< input type = " radio " name = " frequency " value = " monthly " class = " sr-only " / >
< span > Monthly < / span >
< / label >
< label
2023-10-17 13:36:25 +03:00
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 " } " }
2023-10-03 13:36:22 +03:00
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 """
2023-10-17 13:36:25 +03:00
< span class = " absolute -right-16 -top-3 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 >
2023-10-03 13:36:22 +03:00
"""
end
defp slider ( assigns ) do
~H """
2023-10-17 13:36:25 +03:00
< form class = " w-full lg:w-2/5 mt-4 " >
< p class = " font-medium leading-6 text-gray-600 dark:text-gray-200 " >
< b id = " slider-value " class = " text-xl text-gray-900 dark:text-gray-100 " >
< % = slider_value ( @selected_volume , @available_volumes ) % >
< / b >
monthly pageviews
2023-10-03 13:36:22 +03:00
< / p >
< input
phx - change = " slide "
name = " slider "
2023-10-17 13:36:25 +03:00
class = " mt-4 shadow dark:bg-gray-600 dark:border-none "
2023-10-03 13:36:22 +03:00
type = " range "
min = " 0 "
max = { length ( @available_volumes ) }
step = " 1 "
value = {
Enum . find_index ( @available_volumes , & ( &1 == @selected_volume ) ) || length ( @available_volumes )
}
/ >
< / form >
"""
end
defp plan_box ( assigns ) do
2023-11-02 17:46:14 +03:00
highlight =
cond do
assigns . owned -> " Current "
assigns . recommended -> " Recommended "
true -> nil
end
assigns = assign ( assigns , :highlight , highlight )
2023-10-03 13:36:22 +03:00
~H """
< div
id = { " #{ @kind } -plan-box " }
class = { [
2023-10-17 13:36:25 +03:00
" shadow-lg bg-white rounded-3xl px-6 sm:px-8 py-4 sm:py-6 dark:bg-gray-800 " ,
2023-11-02 17:46:14 +03:00
! @highlight && " dark:ring-gray-600 " ,
@highlight && " ring-2 ring-indigo-600 dark:ring-indigo-300 "
2023-10-03 13:36:22 +03:00
] }
>
< div class = " flex items-center justify-between gap-x-4 " >
< h3 class = { [
" text-lg font-semibold leading-8 " ,
2023-11-02 17:46:14 +03:00
! @highlight && " text-gray-900 dark:text-gray-100 " ,
@highlight && " text-indigo-600 dark:text-indigo-300 "
2023-10-03 13:36:22 +03:00
] } >
< % = String . capitalize ( to_string ( @kind ) ) % >
< / h3 >
2023-11-02 17:46:14 +03:00
< . pill :if = { @highlight } text = { @highlight } / >
2023-10-03 13:36:22 +03:00
< / div >
< div >
< . render_price_info available = { @available } { assigns } / >
2023-10-26 18:20:38 +03:00
< % = if @available do % >
< . checkout id = { " #{ @kind } -checkout " } { assigns } / >
< % else % >
< . contact_button class = " bg-indigo-600 hover:bg-indigo-500 text-white " / >
2023-10-03 13:36:22 +03:00
< % end % >
< / div >
2023-11-06 17:01:55 +03:00
< % = if @owned && @kind == :growth && @plan_to_render . generation < 4 do % >
2023-10-23 19:42:00 +03:00
< . growth_grandfathering_notice / >
< % else % >
< ul
role = " list "
class = " mt-8 space-y-3 text-sm leading-6 text-gray-600 dark:text-gray-100 xl:mt-10 "
>
< . plan_benefit :for = { benefit <- @benefits } > < % = benefit % > < / . plan_benefit >
< / ul >
< % end % >
2023-10-03 13:36:22 +03:00
< / div >
"""
end
2023-10-26 18:20:38 +03:00
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 )
2023-11-03 22:49:21 +03:00
exceeded_limits = Quota . exceeded_limits ( assigns . usage , assigns . plan_to_render )
usage_exceeds_plan_limits =
Enum . any? ( [ :team_member_limit , :site_limit ] , & ( &1 in exceeded_limits ) )
2023-10-26 18:20:38 +03:00
2023-11-14 10:40:20 +03:00
subscription = assigns . user . subscription
2023-10-26 18:20:38 +03:00
billing_details_expired =
2023-11-14 10:40:20 +03:00
subscription &&
subscription . status in [
2023-10-26 18:20:38 +03:00
Subscription.Status . paused ( ) ,
Subscription.Status . past_due ( )
]
2023-11-14 10:40:20 +03:00
subscription_cancelled = subscription && subscription . status == Subscription.Status . deleted ( )
2023-10-26 18:20:38 +03:00
{ checkout_disabled , disabled_message } =
cond do
2023-11-14 10:40:20 +03:00
change_plan_link_text == " Currently on this plan " && not subscription_cancelled ->
2023-10-26 18:20:38 +03:00
{ true , nil }
2023-11-03 22:49:21 +03:00
assigns . available && usage_exceeds_plan_limits ->
2023-10-26 18:20:38 +03:00
{ 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 % >
< . paddle_button { assigns } > Upgrade < / . 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 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
2023-10-23 19:42:00 +03:00
defp growth_grandfathering_notice ( assigns ) do
~H """
< ul class = " mt-8 space-y-3 text-sm leading-6 text-gray-600 text-justify dark:text-gray-100 xl:mt-10 " >
Your subscription has been grandfathered in at the same rate and terms as when you first joined . If you don ' t need the "Business" features, you ' re welcome to stay on this plan . You can adjust the pageview limit or change the billing frequency of this grandfathered plan . If you ' re interested in business features, you can upgrade to the new "Business" plan.
< / ul >
"""
end
2023-10-03 13:36:22 +03:00
def render_price_info ( %{ available : false } = assigns ) do
~H """
2023-10-23 19:42:00 +03:00
< p id = { " #{ @kind } -custom-price " } class = " mt-6 flex items-baseline gap-x-1 " >
2023-10-03 13:36:22 +03:00
< 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
def 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 change_plan_link ( assigns ) do
2023-11-06 17:01:55 +03:00
confirmed =
if assigns . confirm_message , do : " confirm( \" #{ assigns . confirm_message } \" ) " , else : " true "
assigns = assign ( assigns , :confirmed , confirmed )
2023-10-03 13:36:22 +03:00
~H """
2023-11-06 17:01:55 +03:00
< button
2023-10-03 13:36:22 +03:00
id = { " #{ @kind } -checkout " }
2023-11-06 17:01:55 +03:00
onclick = { " if ( #{ @confirmed } ) {window.location = ' #{ Routes . billing_path ( PlausibleWeb.Endpoint , :change_plan_preview , @paddle_product_id ) } '} " }
2023-10-03 13:36:22 +03:00
class = { [
" w-full mt-6 block rounded-md py-2 px-3 text-center text-sm font-semibold leading-6 text-white " ,
2023-10-26 18:20:38 +03:00
! @checkout_disabled && " bg-indigo-600 hover:bg-indigo-500 " ,
@checkout_disabled && " pointer-events-none bg-gray-400 dark:bg-gray-600 "
2023-10-03 13:36:22 +03:00
] }
>
2023-10-26 18:20:38 +03:00
< % = @change_plan_link_text % >
2023-11-06 17:01:55 +03:00
< / button >
2023-10-03 13:36:22 +03:00
"""
end
2023-10-23 19:42:00 +03:00
slot :inner_block , required : true
attr :icon_color , :string , default : " indigo-600 "
defp plan_benefit ( assigns ) do
~H """
< li class = " flex gap-x-3 " >
< . check_icon class = { " text- #{ @icon_color } dark:text-green-600 " } / >
< % = render_slot ( @inner_block ) % >
< / li >
"""
end
2023-10-03 13:36:22 +03:00
defp contact_button ( assigns ) do
~H """
< . link
href = { contact_link ( ) }
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
defp enterprise_plan_box ( assigns ) do
~H """
2023-10-23 19:42:00 +03:00
< 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 "
>
2023-10-03 13:36:22 +03:00
< 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 = " " / >
2023-10-18 11:29:13 +03:00
< ul
role = " list "
class = " mt-8 space-y-3 text-sm leading-6 xl:mt-10 text-gray-300 dark:text-gray-100 "
>
2023-10-23 19:42:00 +03:00
< . plan_benefit :for = { benefit <- @benefits } >
< % = if is_binary ( benefit ) , do : benefit , else : benefit . ( assigns ) % >
< / . plan_benefit >
2023-10-03 13:36:22 +03:00
< / ul >
< / div >
"""
end
2023-11-02 17:46:14 +03:00
defp pill ( assigns ) do
2023-10-03 13:36:22 +03:00
~H """
< div class = " flex items-center justify-between gap-x-4 " >
< p
2023-11-02 17:46:14 +03:00
id = " highlight-pill "
2023-10-17 13:36:25 +03:00
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 "
2023-10-03 13:36:22 +03:00
>
2023-11-02 17:46:14 +03:00
< % = @text % >
2023-10-03 13:36:22 +03:00
< / p >
< / div >
"""
end
defp check_icon ( assigns ) do
~H """
< svg { %{ class : " h-6 w-5 flex-none #{ @class } " , viewBox : " 0 0 20 20 " , fill : " currentColor " , " aria-hidden " : " true " } } >
< path
fill - rule = " evenodd "
d = " M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z "
clip - rule = " evenodd "
/ >
< / svg >
"""
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 price_tag ( %{ plan_to_render : % Plan { monthly_cost : nil } } = assigns ) do
~H """
2023-10-23 19:42:00 +03:00
< span class = " text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100 " >
2023-10-03 13:36:22 +03:00
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 |> 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 ) |> 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 |> format_price ( ) % >
< / span >
< span id = { " #{ @kind } -price-tag-interval " } class = " text-sm font-semibold leading-6 text-gray-600 " >
/ year
< / span >
"""
end
defp slider_styles ( assigns ) do
~H """
< style >
input [ type = " range " ] {
- moz - appearance : none ;
- webkit - appearance : none ;
background : white ;
border - radius : 3 px ;
height : 6 px ;
width : 100 % ;
2023-10-17 13:36:25 +03:00
margin - bottom : 9 px ;
2023-10-03 13:36:22 +03:00
outline : none ;
}
input [ type = " range " ] :: - webkit - slider - thumb {
appearance : none ;
- webkit - appearance : none ;
background - color : #5f48ff;
background - image : url ( " data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2212%22%20height%3D%228%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8%20.5v7L12%204zM0%204l4%203.5v-7z%22%20fill%3D%22%23FFFFFF%22%20fill-rule%3D%22nonzero%22%2F%3E%3C%2Fsvg%3E " ) ;
background - position : center ;
background - repeat : no - repeat ;
border : 0 ;
border - radius : 50 % ;
cursor : pointer ;
2023-10-17 13:36:25 +03:00
height : 28 px ;
width : 28 px ;
2023-10-03 13:36:22 +03:00
}
input [ type = " range " ] :: - moz - range - thumb {
background - color : #5f48ff;
background - image : url ( " data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2212%22%20height%3D%228%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8%20.5v7L12%204zM0%204l4%203.5v-7z%22%20fill%3D%22%23FFFFFF%22%20fill-rule%3D%22nonzero%22%2F%3E%3C%2Fsvg%3E " ) ;
background - position : center ;
background - repeat : no - repeat ;
border : 0 ;
border : none ;
border - radius : 50 % ;
cursor : pointer ;
2023-10-17 13:36:25 +03:00
height : 28 px ;
width : 28 px ;
2023-10-03 13:36:22 +03:00
}
input [ type = " range " ] :: - ms - thumb {
background - color : #5f48ff;
background - image : url ( " data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2212%22%20height%3D%228%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8%20.5v7L12%204zM0%204l4%203.5v-7z%22%20fill%3D%22%23FFFFFF%22%20fill-rule%3D%22nonzero%22%2F%3E%3C%2Fsvg%3E " ) ;
background - position : center ;
background - repeat : no - repeat ;
border : 0 ;
border - radius : 50 % ;
cursor : pointer ;
2023-10-17 13:36:25 +03:00
height : 28 px ;
width : 28 px ;
2023-10-03 13:36:22 +03:00
}
input [ type = " range " ] :: - moz - focus - outer {
border : 0 ;
}
< / style >
"""
end
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
defp change_plan_link_text (
2023-10-26 18:20:38 +03:00
%{
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
2023-10-03 13:36:22 +03:00
) 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
2023-10-26 18:20:38 +03:00
defp change_plan_link_text ( _ ) , do : nil
2023-10-03 13:36:22 +03:00
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 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 slider_value ( :enterprise , available_volumes ) do
List . last ( available_volumes )
|> PlausibleWeb.StatsView . large_number_format ( )
|> Kernel . <> ( " + " )
end
defp slider_value ( volume , _ ) when is_integer ( volume ) do
PlausibleWeb.StatsView . large_number_format ( volume )
end
2023-10-23 19:42:00 +03:00
defp growth_benefits ( plan ) do
[
team_member_limit_benefit ( plan ) ,
site_limit_benefit ( plan ) ,
" Intuitive, fast and privacy-friendly dashboard " ,
" Email/Slack reports " ,
" Google Analytics import "
]
|> Kernel . ++ ( feature_benefits ( plan ) )
end
defp business_benefits ( plan , growth_benefits ) do
[
" Everything in Growth " ,
team_member_limit_benefit ( plan ) ,
site_limit_benefit ( plan )
]
|> Kernel . ++ ( feature_benefits ( plan ) )
|> Kernel . -- ( growth_benefits )
|> Kernel . ++ ( [ " Priority support " ] )
end
defp enterprise_benefits ( business_benefits ) do
team_members =
if " Up to 10 team members " in business_benefits ,
do : " 10+ team members " ,
else : nil
[
" Everything in Business " ,
team_members ,
" 50+ sites " ,
2023-11-08 16:24:30 +03:00
" 600+ Stats API requests per hour " ,
2023-10-23 19:42:00 +03:00
& sites_api_benefit / 1 ,
" Technical onboarding "
]
|> Enum . filter ( & &1 )
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 . map ( plan . features , fn feature_mod ->
case feature_mod . name ( ) do
:goals -> " Goals and custom events "
2023-11-08 16:24:30 +03:00
:stats_api -> " Stats API (600 requests per hour) "
2023-10-23 19:42:00 +03:00
: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
2023-10-03 13:36:22 +03:00
defp contact_link ( ) , do : @contact_link
defp billing_faq_link ( ) , do : @billing_faq_link
end