diff --git a/extra/lib/plausible_web/live/customer_support/team.ex b/extra/lib/plausible_web/live/customer_support/team.ex index d2ca34d1353e..bdcb8d505cb1 100644 --- a/extra/lib/plausible_web/live/customer_support/team.ex +++ b/extra/lib/plausible_web/live/customer_support/team.ex @@ -277,7 +277,9 @@ defmodule PlausibleWeb.Live.CustomerSupport.Team do cond do team && team.subscription -> status_str = - PlausibleWeb.SettingsView.present_subscription_status(team.subscription.status) + PlausibleWeb.Components.Billing.Helpers.present_subscription_status( + team.subscription.status + ) if team.subscription.paddle_subscription_id do assigns = %{status_str: status_str, subscription: team.subscription} diff --git a/lib/plausible_web/components/billing/billing.ex b/lib/plausible_web/components/billing/billing.ex index e60cb2fb77de..41489d57047a 100644 --- a/lib/plausible_web/components/billing/billing.ex +++ b/lib/plausible_web/components/billing/billing.ex @@ -6,7 +6,6 @@ defmodule PlausibleWeb.Components.Billing do import PlausibleWeb.Components.Icons - require Plausible.Billing.Subscription.Status alias Plausible.Billing.{Plan, Plans, EnterprisePlan} attr :site, Plausible.Site, required: false, default: nil diff --git a/lib/plausible_web/components/billing/helpers.ex b/lib/plausible_web/components/billing/helpers.ex new file mode 100644 index 000000000000..f2b2028fe2aa --- /dev/null +++ b/lib/plausible_web/components/billing/helpers.ex @@ -0,0 +1,63 @@ +defmodule PlausibleWeb.Components.Billing.Helpers do + @moduledoc false + + require Plausible.Billing.Subscription.Status + alias Plausible.Billing.{Plan, Plans, EnterprisePlan, Subscription, Subscriptions} + + def present_plan_name(%Plan{kind: kind}), + do: kind |> to_string() |> String.capitalize() + + def present_plan_name(%EnterprisePlan{}), do: "Enterprise" + def present_plan_name(:free_10k), do: "Free" + def present_plan_name(_), do: "Plan" + + def present_subscription_interval(subscription) do + case Plans.subscription_interval(subscription) do + "monthly" -> "month" + "yearly" -> "year" + interval -> interval + end + end + + @spec present_subscription_status(Subscription.Status.status()) :: String.t() + def present_subscription_status(Subscription.Status.active()), do: "Active" + def present_subscription_status(Subscription.Status.past_due()), do: "Past due" + def present_subscription_status(Subscription.Status.deleted()), do: "Cancelled" + def present_subscription_status(Subscription.Status.paused()), do: "Paused" + def present_subscription_status(status), do: status + + def subscription_pill_color(Subscription.Status.active()), do: :green + def subscription_pill_color(Subscription.Status.past_due()), do: :yellow + def subscription_pill_color(Subscription.Status.paused()), do: :red + def subscription_pill_color(Subscription.Status.deleted()), do: :red + def subscription_pill_color(_), do: :gray + + def trial_button_label(team) do + if Plausible.Teams.Billing.enterprise_configured?(team) do + "Upgrade" + else + "Choose a plan →" + end + end + + def change_plan_button_label(nil), do: "Upgrade" + + def change_plan_button_label(subscription) do + if Subscriptions.resumable?(subscription) && subscription.cancel_url do + "Change plan" + else + "Upgrade" + end + end + + def format_invoices(invoice_list) do + Enum.map(invoice_list, fn invoice -> + %{ + date: invoice["payout_date"] |> Date.from_iso8601!() |> Calendar.strftime("%b %-d, %Y"), + amount: (invoice["amount"] / 1) |> :erlang.float_to_binary(decimals: 2), + currency: invoice["currency"] |> PlausibleWeb.BillingView.present_currency(), + url: invoice["receipt_url"] + } + end) + end +end diff --git a/lib/plausible_web/controllers/settings_controller.ex b/lib/plausible_web/controllers/settings_controller.ex index aa39e396ad04..8877e31da398 100644 --- a/lib/plausible_web/controllers/settings_controller.ex +++ b/lib/plausible_web/controllers/settings_controller.ex @@ -134,47 +134,6 @@ defmodule PlausibleWeb.SettingsController do render_security(conn) end - def subscription(conn, _params) do - team = conn.assigns.current_team - subscription = Teams.Billing.get_subscription(team) - - invoices = Plausible.Billing.paddle_api().get_invoices(subscription) - - pageview_usage = Teams.Billing.monthly_pageview_usage(team) - site_usage = Teams.Billing.site_usage(team) - team_member_usage = Teams.Billing.team_member_usage(team) - - usage = %{ - monthly_pageviews: pageview_usage, - sites: site_usage, - team_members: team_member_usage - } - - notification_type = Plausible.Billing.Quota.usage_notification_type(team, usage) - - total_pageview_usage_domain = - if site_usage == 1 do - [site] = Plausible.Teams.owned_sites(team) - site.domain - else - on_ee(do: team && get_consolidated_view_domain(team), else: nil) - end - - render(conn, :subscription, - layout: {PlausibleWeb.LayoutView, :settings}, - subscription: subscription, - invoices: invoices, - pageview_limit: Teams.Billing.monthly_pageview_limit(subscription), - pageview_usage: pageview_usage, - site_usage: site_usage, - site_limit: Teams.Billing.site_limit(team), - team_member_limit: Teams.Billing.team_member_limit(team), - team_member_usage: team_member_usage, - notification_type: notification_type, - total_pageview_usage_domain: total_pageview_usage_domain - ) - end - def redirect_invoices(conn, _params) do redirect(conn, to: Routes.settings_path(conn, :subscription)) end @@ -470,15 +429,6 @@ defmodule PlausibleWeb.SettingsController do end end - on_ee do - defp get_consolidated_view_domain(team) do - case Plausible.ConsolidatedView.get(team) do - nil -> nil - view -> if Plausible.ConsolidatedView.ok_to_display?(team), do: view.domain - end - end - end - defp handle_email_updated(conn) do conn |> put_flash(:success, "Email updated") diff --git a/lib/plausible_web/live/components/form.ex b/lib/plausible_web/live/components/form.ex index 0a637312d0e0..c49689be154f 100644 --- a/lib/plausible_web/live/components/form.ex +++ b/lib/plausible_web/live/components/form.ex @@ -428,7 +428,7 @@ defmodule PlausibleWeb.Live.Components.Form do end) end - attr :conn, Plug.Conn, required: true + attr :conn, :map, default: %{} attr :name, :string, required: true attr :options, :list, required: true attr :value, :any, default: nil diff --git a/lib/plausible_web/live/settings_context.ex b/lib/plausible_web/live/settings_context.ex new file mode 100644 index 000000000000..4284f3286439 --- /dev/null +++ b/lib/plausible_web/live/settings_context.ex @@ -0,0 +1,16 @@ +defmodule PlausibleWeb.Live.SettingsContext do + @moduledoc false + + import Phoenix.LiveView + import Phoenix.Component + + def on_mount(_arg, _params, _session, socket) do + socket = attach_hook(socket, :get_current_path, :handle_params, &get_current_path/3) + {:cont, socket, layout: {PlausibleWeb.LayoutView, :settings}} + end + + defp get_current_path(_params, url, socket) do + %{path: current_path} = URI.parse(url) + {:cont, assign(socket, :current_path, current_path)} + end +end diff --git a/lib/plausible_web/live/subscription_settings.ex b/lib/plausible_web/live/subscription_settings.ex new file mode 100644 index 000000000000..470c1cd4adf1 --- /dev/null +++ b/lib/plausible_web/live/subscription_settings.ex @@ -0,0 +1,309 @@ +defmodule PlausibleWeb.Live.SubscriptionSettings do + @moduledoc """ + LiveView for the subscription settings page. + """ + use PlausibleWeb, :live_view + use Plausible + + import PlausibleWeb.Components.Billing.Helpers + + alias Plausible.Teams + alias PlausibleWeb.Router.Helpers, as: Routes + + def mount(_params, _session, socket) do + team = socket.assigns.current_team + subscription = Teams.Billing.get_subscription(team) + invoices = Plausible.Billing.paddle_api().get_invoices(subscription) + pageview_usage = Teams.Billing.monthly_pageview_usage(team) + site_usage = Teams.Billing.site_usage(team) + team_member_usage = Teams.Billing.team_member_usage(team) + + usage = %{ + monthly_pageviews: pageview_usage, + sites: site_usage, + team_members: team_member_usage + } + + notification_type = Plausible.Billing.Quota.usage_notification_type(team, usage) + + total_pageview_usage_domain = + if site_usage == 1 do + [site] = Plausible.Teams.owned_sites(team) + site.domain + else + on_ee(do: team && consolidated_view_domain(team), else: nil) + end + + socket = + socket + |> assign(:subscription, subscription) + |> assign(:invoices, invoices) + |> assign(:pageview_limit, Teams.Billing.monthly_pageview_limit(subscription)) + |> assign(:pageview_usage, pageview_usage) + |> assign(:site_usage, site_usage) + |> assign(:site_limit, Teams.Billing.site_limit(team)) + |> assign(:team_member_limit, Teams.Billing.team_member_limit(team)) + |> assign(:team_member_usage, team_member_usage) + |> assign(:notification_type, notification_type) + |> assign(:total_pageview_usage_domain, total_pageview_usage_domain) + + {:ok, socket} + end + + def render(assigns) do + ~H""" + <.settings_tiles> + <%= if is_nil(@current_team) || Plausible.Teams.on_trial?(@current_team) do %> + <.tile docs="trial-to-paid"> + <:title>Current plan +
+
+
+ Free trial + <.pill :if={@current_team} color={:yellow}> + {Plausible.Teams.trial_days_left(@current_team)} days left + +
+

+ Your 30-day trial will start when you add your first site +

+
+ <.button_link + href={Routes.billing_path(PlausibleWeb.Endpoint, :choose_plan)} + mt?={false} + id="upgrade-or-change-plan-link" + > + {trial_button_label(@current_team)} + +
+ + <% else %> + <.tile docs="billing"> + <:title>Current plan + <%= if @subscription do %> +
+ +
+
+
+ + {present_plan_name(Plausible.Billing.Plans.get_subscription_plan(@subscription))} + + <.pill color={subscription_pill_color(@subscription.status)}> + {present_subscription_status(@subscription.status)} + +
+

+ Up to {PlausibleWeb.AuthView.subscription_quota(@subscription)} monthly pageviews +

+ <%= if @subscription.next_bill_amount && @subscription.next_bill_date do %> +

+ {PlausibleWeb.BillingView.present_currency(@subscription.currency_code)}{@subscription.next_bill_amount} / {present_subscription_interval( + @subscription + )} • Renews on {Calendar.strftime(@subscription.next_bill_date, "%b %-d, %Y")} +

+ <% end %> +
+
+ <.button_link + :if={ + @subscription && + Plausible.Billing.Subscriptions.resumable?(@subscription) && + @subscription.update_url + } + theme="secondary" + href={@subscription.update_url} + mt?={false} + id="billing-details-link" + > + Billing details + + <.button_link + :if={ + not (Plausible.Teams.Billing.enterprise_configured?(@current_team) && + Plausible.Billing.Subscriptions.halted?(@subscription)) + } + href={Routes.billing_path(PlausibleWeb.Endpoint, :choose_plan)} + mt?={false} + id="upgrade-or-change-plan-link" + > + {change_plan_button_label(@subscription)} + +
+
+
+ <% else %> + + <% end %> + + <% end %> + + <.tile docs="subscription-plans"> + <:title> + Monthly usage + + +
+ + +
+ + + <.tile docs="subscription-plans"> + <:title>Site and team usage + +
+ +
+
+ +
+ Owned sites + + {PlausibleWeb.TextHelpers.number_format(@site_usage)} + {if is_number(@site_limit), + do: "/ #{PlausibleWeb.TextHelpers.number_format(@site_limit)}"} + {if @site_limit == :unlimited, do: "/ Unlimited"} + +
+
+
+ +
+ Team members + + {PlausibleWeb.TextHelpers.number_format(@team_member_usage)} + {if is_number(@team_member_limit), + do: "/ #{PlausibleWeb.TextHelpers.number_format(@team_member_limit)}"} + {if @team_member_limit == :unlimited, do: "/ Unlimited"} + +
+
+
+
+ + + <.tile :if={@subscription} docs="download-invoices"> + <:title> + Invoices + + <%= case @invoices do %> + <% {:error, :no_invoices} -> %> +

+ You don't have any invoices yet. +

+ <% {:error, :request_failed} -> %> + <.notice theme={:gray} title="We couldn't retrieve your invoices"> + Please refresh the page or try again later + + <% {:ok, invoice_list} when is_list(invoice_list) -> %> +
+ <.table + rows={Enum.with_index(format_invoices(invoice_list))} + row_attrs={ + fn {_invoice, idx} -> + %{ + "x-show" => "showAll || #{idx} < 3" + } + end + } + > + <:tbody :let={{invoice, _idx}}> + <.td>{invoice.date} + <.td>{invoice.currency <> invoice.amount} + <.td class="flex justify-end"> + <.styled_link href={invoice.url} new_tab={true}> + + + 3}> + + <.button + theme="secondary" + x-on:click="showAll = true" + x-show="!showAll" + > + Show more + + + + +
+ <% end %> + + + + <%= if Plausible.Billing.Subscriptions.resumable?(@subscription) && @subscription.cancel_url do %> +
+ <.button_link theme="danger" href={@subscription.cancel_url} mt?={false}> + Cancel plan + + <%= if Application.get_env(:plausible, :environment) == "dev" do %> + <.button_link + href={@subscription.update_url} + theme="secondary" + class="text-yellow-600 dark:text-yellow-400" + mt?={false} + > + [DEV ONLY] Change status + + <% end %> +
+ <% end %> + """ + end + + on_ee do + defp consolidated_view_domain(team) do + view = Plausible.ConsolidatedView.get(team) + + if not is_nil(view) and Plausible.ConsolidatedView.ok_to_display?(team) do + view.domain + end + end + end +end diff --git a/lib/plausible_web/plugs/current_path.ex b/lib/plausible_web/plugs/current_path.ex new file mode 100644 index 000000000000..fecc537e6caf --- /dev/null +++ b/lib/plausible_web/plugs/current_path.ex @@ -0,0 +1,6 @@ +defmodule PlausibleWeb.Plugs.CurrentPath do + @moduledoc false + + def init(_), do: [] + def call(conn, _), do: Plug.Conn.assign(conn, :current_path, conn.request_path) +end diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex index d864985e87c1..9f5265bd0163 100644 --- a/lib/plausible_web/router.ex +++ b/lib/plausible_web/router.ex @@ -481,7 +481,12 @@ defmodule PlausibleWeb.Router do end scope "/settings", PlausibleWeb do - pipe_through [:browser, :csrf, PlausibleWeb.RequireAccountPlug] + pipe_through [ + :browser, + :csrf, + PlausibleWeb.RequireAccountPlug, + PlausibleWeb.Plugs.CurrentPath + ] get "/", SettingsController, :index get "/preferences", SettingsController, :preferences @@ -496,7 +501,12 @@ defmodule PlausibleWeb.Router do post "/security/email", SettingsController, :update_email post "/security/password", SettingsController, :update_password - get "/billing/subscription", SettingsController, :subscription + live_session :settings, on_mount: PlausibleWeb.Live.SettingsContext do + scope alias: Live, assigns: %{connect_live_socket: true} do + live "/billing/subscription", SubscriptionSettings, :subscription, as: :settings + end + end + get "/billing/invoices", SettingsController, :redirect_invoices get "/api-keys", SettingsController, :api_keys diff --git a/lib/plausible_web/templates/layout/settings.html.heex b/lib/plausible_web/templates/layout/settings.html.heex index 791478790180..b7e3f4d34f1e 100644 --- a/lib/plausible_web/templates/layout/settings.html.heex +++ b/lib/plausible_web/templates/layout/settings.html.heex @@ -1,4 +1,4 @@ -<% options = account_settings_sidebar(@conn) %> +<% options = account_settings_sidebar(assigns) %>
<.styled_link class="font-semibold text-sm" href="/sites"> ← Back to sites @@ -13,8 +13,7 @@ <.mobile_nav_dropdown name="settings" options={options} - selected_fn={&is_current_tab(@conn, &1)} - conn={@conn} + selected_fn={&is_current_tab(@current_path, &1)} href_base="/settings/" /> @@ -27,7 +26,7 @@

@@ -40,7 +39,7 @@

diff --git a/lib/plausible_web/templates/settings/subscription.html.heex b/lib/plausible_web/templates/settings/subscription.html.heex deleted file mode 100644 index b7a3e563e7e5..000000000000 --- a/lib/plausible_web/templates/settings/subscription.html.heex +++ /dev/null @@ -1,242 +0,0 @@ -<.settings_tiles> - <%= if is_nil(@current_team) || Plausible.Teams.on_trial?(@current_team) do %> - <.tile docs="trial-to-paid"> - <:title>Current plan -
-
-
- Free trial - <.pill :if={@current_team} color={:yellow}> - {Plausible.Teams.trial_days_left(@current_team)} days left - -
-

- Your 30-day trial will start when you add your first site -

-
- <.button_link - href={Routes.billing_path(@conn, :choose_plan)} - mt?={false} - id="upgrade-or-change-plan-link" - > - {trial_button_label(@current_team)} - -
- - <% else %> - <.tile docs="billing"> - <:title>Current plan - <%= if @subscription do %> -
- -
-
-
- - {present_plan_name(Plausible.Billing.Plans.get_subscription_plan(@subscription))} - - <.pill color={subscription_pill_color(@subscription.status)}> - {present_subscription_status(@subscription.status)} - -
-

- Up to {PlausibleWeb.AuthView.subscription_quota(@subscription)} monthly pageviews -

- <%= if @subscription.next_bill_amount && @subscription.next_bill_date do %> -

- {PlausibleWeb.BillingView.present_currency(@subscription.currency_code)}{@subscription.next_bill_amount} / {present_subscription_interval( - @subscription - )} • Renews on {Calendar.strftime(@subscription.next_bill_date, "%b %-d, %Y")} -

- <% end %> -
-
- <.button_link - :if={ - @subscription && - Plausible.Billing.Subscriptions.resumable?(@subscription) && - @subscription.update_url - } - theme="secondary" - href={@subscription.update_url} - mt?={false} - id="billing-details-link" - > - Billing details - - <.button_link - :if={ - not (Plausible.Teams.Billing.enterprise_configured?(@current_team) && - Plausible.Billing.Subscriptions.halted?(@subscription)) - } - href={Routes.billing_path(@conn, :choose_plan)} - mt?={false} - id="upgrade-or-change-plan-link" - > - {change_plan_button_label(@subscription)} - -
-
-
- <% else %> - - <% end %> - - <% end %> - - <.tile docs="subscription-plans"> - <:title> - Monthly usage - - -
- - -
- - - <.tile docs="subscription-plans"> - <:title>Site and team usage - -
- -
-
- -
- Owned sites - - {PlausibleWeb.TextHelpers.number_format(@site_usage)} - {if is_number(@site_limit), - do: "/ #{PlausibleWeb.TextHelpers.number_format(@site_limit)}"} - {if @site_limit == :unlimited, do: "/ Unlimited"} - -
-
-
- -
- Team members - - {PlausibleWeb.TextHelpers.number_format(@team_member_usage)} - {if is_number(@team_member_limit), - do: "/ #{PlausibleWeb.TextHelpers.number_format(@team_member_limit)}"} - {if @team_member_limit == :unlimited, do: "/ Unlimited"} - -
-
-
-
- - - <.tile :if={@subscription} docs="download-invoices"> - <:title> - Invoices - - <%= case @invoices do %> - <% {:error, :no_invoices} -> %> -

- You don't have any invoices yet. -

- <% {:error, :request_failed} -> %> - <.notice theme={:gray} title="We couldn't retrieve your invoices"> - Please refresh the page or try again later - - <% {:ok, invoice_list} when is_list(invoice_list) -> %> -
- <.table - rows={Enum.with_index(format_invoices(invoice_list))} - row_attrs={ - fn {_invoice, idx} -> - %{ - "x-show" => "showAll || #{idx} < 3" - } - end - } - > - <:tbody :let={{invoice, _idx}}> - <.td>{invoice.date} - <.td>{invoice.currency <> invoice.amount} - <.td class="flex justify-end"> - <.styled_link href={invoice.url} new_tab={true}> - - - 3}> - - <.button - theme="secondary" - x-on:click="showAll = true" - x-show="!showAll" - > - Show more - - - - -
- <% end %> - - - -<%= if Plausible.Billing.Subscriptions.resumable?(@subscription) && @subscription.cancel_url do %> -
- <.button_link theme="danger" href={@subscription.cancel_url} mt?={false}> - Cancel plan - - <%= if Application.get_env(:plausible, :environment) == "dev" do %> - <.button_link - href={@subscription.update_url} - theme="secondary" - class="text-yellow-600 dark:text-yellow-400" - mt?={false} - > - [DEV ONLY] Change status - - <% end %> -
-<% end %> diff --git a/lib/plausible_web/views/layout_view.ex b/lib/plausible_web/views/layout_view.ex index f224da978726..aee6bd45c126 100644 --- a/lib/plausible_web/views/layout_view.ex +++ b/lib/plausible_web/views/layout_view.ex @@ -95,9 +95,9 @@ defmodule PlausibleWeb.LayoutView do |> Enum.reject(&is_nil/1) end - def account_settings_sidebar(conn) do - current_team = conn.assigns[:current_team] - current_team_role = conn.assigns[:current_team_role] + def account_settings_sidebar(assigns) do + current_team = assigns.current_team + current_team_role = assigns.current_team_role options = %{ "Account" => @@ -110,7 +110,7 @@ defmodule PlausibleWeb.LayoutView do if(not Teams.setup?(current_team), do: %{key: "API keys", value: "api-keys", icon: :api_keys} ), - if(Plausible.Users.type(conn.assigns.current_user) == :standard, + if(Plausible.Users.type(assigns.current_user) == :standard, do: %{key: "Danger zone", value: "danger-zone", icon: :exclamation_triangle} ) ] @@ -248,6 +248,10 @@ defmodule PlausibleWeb.LayoutView do false end + def is_current_tab(path, tab) when is_binary(path) do + String.ends_with?(path, tab) + end + def is_current_tab(conn, tab) do full_path = Path.join(conn.path_info) diff --git a/lib/plausible_web/views/settings_view.ex b/lib/plausible_web/views/settings_view.ex index 167115a35969..e4e217de465e 100644 --- a/lib/plausible_web/views/settings_view.ex +++ b/lib/plausible_web/views/settings_view.ex @@ -2,68 +2,4 @@ defmodule PlausibleWeb.SettingsView do use PlausibleWeb, :view use Phoenix.Component, global_prefixes: ~w(x-) use Plausible - - require Plausible.Billing.Subscription.Status - alias Plausible.Billing.{Plans, Subscription, Subscriptions} - - def present_plan_name(%Plausible.Billing.Plan{kind: kind}), - do: kind |> to_string() |> String.capitalize() - - def present_plan_name(%Plausible.Billing.EnterprisePlan{}), do: "Enterprise" - def present_plan_name(:free_10k), do: "Free" - def present_plan_name(_), do: "Plan" - - def subscription_interval(subscription) do - Plans.subscription_interval(subscription) - end - - def present_subscription_interval(subscription) do - case subscription_interval(subscription) do - "monthly" -> "month" - "yearly" -> "year" - interval -> interval - end - end - - @spec present_subscription_status(Subscription.Status.status()) :: String.t() - def present_subscription_status(Subscription.Status.active()), do: "Active" - def present_subscription_status(Subscription.Status.past_due()), do: "Past due" - def present_subscription_status(Subscription.Status.deleted()), do: "Cancelled" - def present_subscription_status(Subscription.Status.paused()), do: "Paused" - def present_subscription_status(status), do: status - - def subscription_pill_color(Subscription.Status.active()), do: :green - def subscription_pill_color(Subscription.Status.past_due()), do: :yellow - def subscription_pill_color(Subscription.Status.paused()), do: :red - def subscription_pill_color(Subscription.Status.deleted()), do: :red - def subscription_pill_color(_), do: :gray - - def trial_button_label(team) do - if Plausible.Teams.Billing.enterprise_configured?(team) do - "Upgrade" - else - "Choose a plan →" - end - end - - def change_plan_button_label(nil), do: "Upgrade" - - def change_plan_button_label(subscription) do - if Subscriptions.resumable?(subscription) && subscription.cancel_url do - "Change plan" - else - "Upgrade" - end - end - - def format_invoices(invoice_list) do - Enum.map(invoice_list, fn invoice -> - %{ - date: invoice["payout_date"] |> Date.from_iso8601!() |> Calendar.strftime("%b %-d, %Y"), - amount: (invoice["amount"] / 1) |> :erlang.float_to_binary(decimals: 2), - currency: invoice["currency"] |> PlausibleWeb.BillingView.present_currency(), - url: invoice["receipt_url"] - } - end) - end end