analytics/lib/plausible_web/templates/auth/user_settings.html.heex
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

499 lines
21 KiB
Plaintext

<div x-data="{disable2FAOpen: false, regenerate2FAOpen: false}">
<%= if full_build?() do %>
<div class="max-w-2xl px-8 pt-4 pb-8 mx-auto mt-24 bg-white border-t-2 border-orange-200 rounded rounded-t-none shadow-md dark:bg-gray-800 dark:border-orange-200 ">
<div class="flex flex-wrap justify-between">
<h2 class="text-xl font-black dark:text-gray-100 w-max mr-4 mt-2">Subscription Plan</h2>
<div class="gap-x-2 mt-2 inline-flex">
<span
:if={@subscription && Plausible.Billing.Plans.business_tier?(@subscription)}
class={[
"w-max px-2.5 py-0.5 rounded-md text-sm font-bold leading-5 text-indigo-600 bg-blue-100 dark:text-yellow-200 dark:border dark:bg-inherit dark:border-yellow-200"
]}
>
Business
</span>
<span
:if={@subscription}
class={[
"w-max px-2.5 py-0.5 rounded-md text-sm font-bold leading-5",
subscription_colors(@subscription.status)
]}
>
<%= present_subscription_status(@subscription.status) %>
</span>
</div>
</div>
<div class="my-4 border-b border-gray-400"></div>
<PlausibleWeb.Components.Billing.Notice.subscription_cancelled
user={@user}
dismissable={false}
/>
<div class="flex flex-col items-center justify-between mt-8 sm:flex-row sm:items-start">
<PlausibleWeb.Components.Billing.monthly_quota_box
user={@user}
subscription={@subscription}
/>
<div
class="h-32 px-2 py-4 my-4 text-center bg-gray-100 rounded dark:bg-gray-900"
style="width: 11.75rem;"
>
<h4 class="font-black dark:text-gray-100">Next bill amount</h4>
<%= if Plausible.Billing.Subscription.Status.in?(@subscription, [Plausible.Billing.Subscription.Status.active(), Plausible.Billing.Subscription.Status.past_due()]) do %>
<div class="py-2 text-xl font-medium dark:text-gray-100">
<%= PlausibleWeb.BillingView.present_currency(@subscription.currency_code) %><%= @subscription.next_bill_amount %>
</div>
<.styled_link :if={@subscription.update_url} href={@subscription.update_url}>
Update billing info
</.styled_link>
<% else %>
<div class="py-2 text-xl font-medium dark:text-gray-100">---</div>
<% end %>
</div>
<div
class="h-32 px-2 py-4 my-4 text-center bg-gray-100 rounded dark:bg-gray-900"
style="width: 11.75rem;"
>
<h4 class="font-black dark:text-gray-100">Next bill date</h4>
<%= if @subscription && @subscription.next_bill_date && Plausible.Billing.Subscription.Status.in?(@subscription, [Plausible.Billing.Subscription.Status.active(), Plausible.Billing.Subscription.Status.past_due()]) do %>
<div class="py-2 text-xl font-medium dark:text-gray-100">
<%= Timex.format!(@subscription.next_bill_date, "{Mshort} {D}, {YYYY}") %>
</div>
<span class="text-gray-600 dark:text-gray-400">
(<%= subscription_interval(@subscription) %> billing)
</span>
<% else %>
<div class="py-2 text-xl font-medium dark:text-gray-100">---</div>
<% end %>
</div>
</div>
<PlausibleWeb.Components.Billing.render_monthly_pageview_usage
usage={@pageview_usage}
limit={@pageview_limit}
/>
<article class="mt-8">
<h1 class="text-xl mb-3 font-bold dark:text-gray-100">Sites & team members usage</h1>
<PlausibleWeb.Components.Billing.usage_and_limits_table>
<PlausibleWeb.Components.Billing.usage_and_limits_row
id="site-usage-row"
title="Owned sites"
usage={@site_usage}
limit={@site_limit}
/>
<PlausibleWeb.Components.Billing.usage_and_limits_row
id="team-member-usage-row"
title="Team members"
usage={@team_member_usage}
limit={@team_member_limit}
/>
</PlausibleWeb.Components.Billing.usage_and_limits_table>
</article>
<%= cond do %>
<% Plausible.Billing.Subscriptions.resumable?(@subscription) && @subscription.cancel_url -> %>
<div class="mt-8">
<%= link("Cancel my subscription",
to: @subscription.cancel_url,
class:
"inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150"
) %>
</div>
<% true -> %>
<div class="mt-8">
<PlausibleWeb.Components.Billing.upgrade_link user={@user} />
</div>
<% end %>
</div>
<%= case @invoices do %>
<% {:error, :no_invoices} -> %>
<% {:error, :request_failed} -> %>
<div class="max-w-2xl px-8 pt-6 pb-8 mx-auto mt-16 bg-white border-t-2 border-orange-200 rounded rounded-t-none shadow-md dark:bg-gray-800">
<h2 class="text-xl font-black dark:text-gray-100">Invoices</h2>
<div class="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<p class="text-center text-black dark:text-gray-100 m-2">
Something went wrong
</p>
</div>
<% {:ok, invoice_list} when is_list(invoice_list) -> %>
<div class="max-w-2xl px-8 pt-6 pb-8 mx-auto mt-16 bg-white border-t-2 border-orange-200 rounded rounded-t-none shadow-md dark:bg-gray-800">
<h2 class="text-xl font-black dark:text-gray-100">Invoices</h2>
<div class="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<table class="min-w-full divide-y divide-gray-200">
<thead>
<tr>
<th
scope="col"
class="py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
>
Date
</th>
<th
scope="col"
class="py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
>
Amount
</th>
<th
scope="col"
class="py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
>
Invoice
</th>
</tr>
</thead>
<%= for invoice <- format_invoices(invoice_list) do %>
<tbody class="divide-y divide-gray-200">
<tr>
<td class="py-4 text-sm text-gray-800 dark:text-gray-200 font-medium">
<%= invoice.date %>
</td>
<td class="py-4 text-sm text-gray-800 dark:text-gray-200">
<%= invoice.currency <> invoice.amount %>
</td>
<td class="py-4 text-sm text-indigo-500">
<%= link("Link", to: invoice.url, target: "_blank") %>
</td>
</tr>
</tbody>
<% end %>
</table>
</div>
<% end %>
<% end %>
<div class="max-w-2xl px-8 pt-6 pb-8 mx-auto mt-16 bg-white border-t-2 border-green-500 rounded rounded-t-none shadow-md dark:bg-gray-800">
<h2 class="text-xl font-black dark:text-gray-100">Dashboard Appearance</h2>
<div class="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<%= form_for @settings_changeset, "/settings", [class: "max-w-sm"], fn f -> %>
<div class="col-span-4 sm:col-span-2">
<%= label(f, :theme, "Theme Selection",
class: "block text-sm font-medium leading-5 text-gray-700 dark:text-gray-300"
) %>
<%= select(f, :theme, Plausible.Themes.options(),
class:
"dark:bg-gray-900 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:text-gray-100 cursor-pointer"
) %>
</div>
<PlausibleWeb.Components.Generic.button type="submit" class="mt-4">
Save
</PlausibleWeb.Components.Generic.button>
<% end %>
</div>
<div class="max-w-2xl px-8 pt-6 pb-8 mx-auto mt-16 bg-white border-t-2 border-green-500 rounded rounded-t-none shadow-md dark:bg-gray-800">
<h2 id="setup-2fa" class="text-xl font-black dark:text-gray-100">
Two-Factor Authentication (2FA)
</h2>
<p class="text-sm mt-2 text-gray-600 dark:text-gray-400">
Two-Factor Authentication protects your account by adding an extra security step when you log in.
</p>
<%= if @totp_enabled? do %>
<button
x-on:click="disable2FAOpen = true; $refs.disable2FAPassword.value = ''"
type="button"
class="inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150"
>
Disable 2FA
</button>
<p class="mt-2 text-gray-600 text-sm dark:text-gray-400">
Lost your recovery codes?
<a
href="#setup-2fa"
x-on:click="regenerate2FAOpen = true; $refs.regenerate2FAPassword.value = ''"
class="underline text-indigo-600"
>
Generate new
</a>
</p>
<% else %>
<%= form_for @conn.params, Routes.auth_path(@conn, :initiate_2fa_setup), fn _ -> %>
<PlausibleWeb.Components.Generic.button type="submit" class="mt-4">
Enable 2FA
</PlausibleWeb.Components.Generic.button>
<% end %>
<% end %>
</div>
<div class="max-w-2xl px-8 pt-6 pb-8 mx-auto mt-16 bg-white border-t-2 border-indigo-100 rounded rounded-t-none shadow-md dark:bg-gray-800 dark:border-indigo-500">
<h2 id="change-account-name" class="text-xl font-black dark:text-gray-100">
Change account name
</h2>
<div class="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<%= form_for @settings_changeset, "/settings#change-account-name", [class: "max-w-sm"], fn f -> %>
<div class="my-4">
<%= label(f, :name, class: "block text-sm font-medium text-gray-700 dark:text-gray-300") %>
<div class="mt-1">
<%= text_input(f, :name,
class:
"shadow-sm dark:bg-gray-900 dark:text-gray-300 focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:bg-gray-800"
) %>
<%= error_tag(f, :name) %>
</div>
</div>
<PlausibleWeb.Components.Generic.button type="submit" class="mt-4">
Save
</PlausibleWeb.Components.Generic.button>
<% end %>
</div>
<div class="max-w-2xl px-8 pt-6 pb-8 mx-auto mt-16 bg-white border-t-2 border-red-600 rounded rounded-t-none shadow-md dark:bg-gray-800">
<h2 id="change-email-address" class="text-xl font-black dark:text-gray-100">
Change email address
</h2>
<div class="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<%= form_for @email_changeset, "/settings/email#change-email-address", [class: "max-w-sm"], fn f -> %>
<div class="my-4">
<%= label(f, :password, "Account password",
class: "block text-sm font-medium text-gray-700 dark:text-gray-300"
) %>
<div class="mt-1">
<%= password_input(f, :password,
class:
"shadow-sm dark:bg-gray-900 dark:text-gray-300 focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:bg-gray-800"
) %>
<%= error_tag(f, :password) %>
</div>
</div>
<div class="my-4">
<%= label(f, :current_email, "Current email",
class: "block text-sm font-medium text-gray-700 dark:text-gray-300"
) %>
<div class="mt-1">
<%= email_input(f, :current_email,
readonly: true,
value: f.data.email,
class:
"shadow-sm dark:bg-gray-900 dark:text-gray-300 focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:bg-gray-800 bg-gray-100"
) %>
</div>
</div>
<div class="my-4">
<%= label(f, :email, "New email",
class: "block text-sm font-medium text-gray-700 dark:text-gray-300"
) %>
<div class="mt-1">
<%= email_input(f, :email,
value: f.params["email"],
class:
"shadow-sm dark:bg-gray-900 dark:text-gray-300 focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md"
) %>
<%= error_tag(f, :email) %>
</div>
</div>
<%= submit("Change my email",
class:
"inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150"
) %>
<% end %>
</div>
<div
id="api-keys"
class="max-w-2xl px-8 pt-6 pb-8 mx-auto mt-16 bg-white border-t-2 border-indigo-100 rounded rounded-t-none shadow-md dark:bg-gray-800 dark:border-indigo-500"
>
<h2 class="text-xl font-black dark:text-gray-100">API Keys</h2>
<div class="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<PlausibleWeb.Components.Billing.Notice.premium_feature
billable_user={@current_user}
current_user={@current_user}
feature_mod={Plausible.Billing.Feature.StatsAPI}
/>
<div class="flex flex-col mt-6">
<div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
<%= if Enum.any?(@user.api_keys) do %>
<div class="overflow-hidden border-b border-gray-200 shadow dark:border-gray-900 sm:rounded-lg">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-900">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th
scope="col"
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-gray-100"
>
Name
</th>
<th
scope="col"
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-gray-100"
>
Key
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">Revoke</span>
</th>
</tr>
</thead>
<tbody>
<%= for api_key <- @user.api_keys do %>
<tr class="bg-white dark:bg-gray-800">
<td class="px-6 py-4 text-sm font-medium text-gray-900 dark:text-gray-100 whitespace-nowrap">
<%= api_key.name %>
</td>
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-100 whitespace-nowrap">
<%= api_key.key_prefix %><%= String.duplicate("*", 32 - 6) %>
</td>
<td class="px-6 py-4 text-sm font-medium text-right whitespace-nowrap">
<%= button("Revoke",
to: "/settings/api-keys/#{api_key.id}",
class: "text-red-600 hover:text-red-900",
method: :delete,
"data-confirm":
"Are you sure you want to revoke this key? This action cannot be reversed."
) %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
<PlausibleWeb.Components.Generic.button_link
:if={Plausible.Billing.Feature.StatsAPI.check_availability(@current_user) == :ok}
href={Routes.auth_path(@conn, :new_api_key)}
class="mt-4"
>
+ New API Key
</PlausibleWeb.Components.Generic.button_link>
</div>
</div>
</div>
</div>
<div class="max-w-2xl px-8 pt-6 pb-8 mx-auto mt-16 mb-24 bg-white border-t-2 border-red-600 rounded rounded-t-none shadow-md dark:bg-gray-800">
<div class="flex items-center justify-between">
<h2 class="text-xl font-black dark:text-gray-100">Delete account</h2>
</div>
<div class="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<p class="dark:text-gray-100">
Deleting your account removes all sites and stats you've collected
</p>
<%= if Plausible.Billing.Subscription.Status.active?(@subscription) do %>
<span class="mt-6 bg-gray-300 button dark:bg-gray-600 hover:shadow-none hover:bg-gray-300 cursor-not-allowed">
Delete my account
</span>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
Your account cannot be deleted because you have an active subscription. If you want to delete your account, please cancel your subscription first.
</p>
<% else %>
<%= link("Delete my account",
to: "/me",
class:
"inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150",
method: "delete",
data: [
confirm:
"Deleting your account will also delete all the sites and data that you own. This action cannot be reversed. Are you sure?"
]
) %>
<% end %>
</div>
<PlausibleWeb.Components.TwoFactor.modal
:let={f}
id="disable-2fa-modal"
state_param="disable2FAOpen"
form_data={@conn.params}
form_target={Routes.auth_path(@conn, :disable_2fa)}
title="Disable Two-Factor Authentication?"
>
<:icon>
<Heroicons.shield_exclamation class="h-6 w-6" />
</:icon>
<:buttons>
<.button type="submit" class="w-full sm:w-auto">
Disable 2FA
</.button>
</:buttons>
<div class="text-sm mt-2">
Once disabled, verification codes from the authenticator application and current recovery codes will become invalid. 2FA will have to be setup from the start.
</div>
<div class="text-sm mt-2">
Enter your password to disable 2FA.
</div>
<div class="mt-3">
<%= password_input(f, :password,
id: "disable_2fa_password",
placeholder: "Enter password",
"x-ref": "disable2FAPassword",
class:
"shadow-sm dark:bg-gray-900 dark:text-gray-300 focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:bg-gray-800"
) %>
</div>
</PlausibleWeb.Components.TwoFactor.modal>
<PlausibleWeb.Components.TwoFactor.modal
:let={f}
id="regenerate-2fa-modal"
state_param="regenerate2FAOpen"
form_data={@conn.params}
form_target={Routes.auth_path(@conn, :generate_2fa_recovery_codes)}
onsubmit="document.getElementById('generate-2fa-recovery-button').disabled = true"
title="Generate New Recovery Codes?"
>
<:icon>
<Heroicons.key class="h-6 w-6" />
</:icon>
<:buttons>
<.button
id="generate-2fa-recovery-button"
type="submit"
class="w-full sm:w-auto [&>span.label-enabled]:block [&>span.label-disabled]:hidden [&[disabled]>span.label-enabled]:hidden [&[disabled]>span.label-disabled]:block"
>
<span class="label-enabled pointer-events-none">
Generate New Codes
</span>
<span class="label-disabled">
<.spinner class="inline-block h-5 w-5 mr-2 text-white dark:text-gray-400" />
Generating Codes
</span>
</.button>
</:buttons>
<div class="text-sm mt-2">
If you generate new codes, the old ones will become invalid.
</div>
<div class="text-sm mt-2">
Enter your password to continue.
</div>
<div class="mt-3 w-full">
<%= password_input(f, :password,
id: "regenerate_2fa_password",
placeholder: "Enter password",
"x-ref": "regenerate2FAPassword",
class:
"w-full shadow-sm dark:bg-gray-900 dark:text-gray-300 focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:bg-gray-800"
) %>
</div>
</PlausibleWeb.Components.TwoFactor.modal>
</div>