Initial commit on Forgejo
Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
This commit is contained in:
commit
af6077b9c3
455 changed files with 78366 additions and 0 deletions
159
lib/tarakan_web/live/billing_live.ex
Normal file
159
lib/tarakan_web/live/billing_live.ex
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
defmodule TarakanWeb.BillingLive do
|
||||
@moduledoc """
|
||||
Account billing page: managed-disclosure retainer status, period end, and
|
||||
the Stripe customer portal exit. No feature gates hang off it.
|
||||
"""
|
||||
|
||||
use TarakanWeb, :live_view
|
||||
|
||||
alias Tarakan.Billing
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
account = socket.assigns.current_scope.account
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:page_title, "Billing")
|
||||
|> assign(:subscription, Billing.subscription(account))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(params, _uri, socket) do
|
||||
socket =
|
||||
case params["checkout"] do
|
||||
"success" -> put_flash(socket, :info, "Subscription started - welcome aboard.")
|
||||
"cancelled" -> put_flash(socket, :info, "Checkout cancelled; nothing was charged.")
|
||||
_other -> socket
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("subscribe", %{"plan" => plan}, socket) do
|
||||
case Billing.ensure_checkout(socket.assigns.current_scope, plan) do
|
||||
{:ok, checkout_url} ->
|
||||
{:noreply, redirect(socket, external: checkout_url)}
|
||||
|
||||
{:error, _reason} ->
|
||||
{:noreply, put_flash(socket, :error, "Checkout could not be started. Try again shortly.")}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("manage", _params, socket) do
|
||||
case Billing.portal_session(socket.assigns.current_scope.account) do
|
||||
{:ok, portal_url} ->
|
||||
{:noreply, redirect(socket, external: portal_url)}
|
||||
|
||||
{:error, _reason} ->
|
||||
{:noreply,
|
||||
put_flash(socket, :error, "The billing portal is unavailable for this account.")}
|
||||
end
|
||||
end
|
||||
|
||||
defp status_label(nil), do: "No subscription"
|
||||
defp status_label(%{status: "active"}), do: "Active"
|
||||
defp status_label(%{status: "past_due"}), do: "Past due - update your payment method"
|
||||
defp status_label(%{status: "canceled"}), do: "Canceled"
|
||||
defp status_label(%{status: "incomplete"}), do: "Incomplete"
|
||||
|
||||
defp plan_label(%{plan: "enterprise"}), do: "Managed disclosure"
|
||||
# Retired tier; kept so historical rows still render.
|
||||
defp plan_label(%{plan: "team"}), do: "Team (retired)"
|
||||
defp plan_label(_other), do: "None"
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<Layouts.app flash={@flash} current_scope={@current_scope} current_path={assigns[:current_path]}>
|
||||
<Layouts.page width={:focused} class="space-y-8">
|
||||
<header class="border-b-2 border-strong pb-6">
|
||||
<h1 class="font-display text-3xl font-medium uppercase leading-none tracking-[0.02em] text-ink">
|
||||
Billing
|
||||
</h1>
|
||||
<p class="mt-3 text-sm leading-6 text-ink-muted">
|
||||
Managed disclosure retainer. Everything else on Tarakan is free.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section id="billing-current" class="border-2 border-strong p-5">
|
||||
<dl class="grid gap-4 sm:grid-cols-3">
|
||||
<div>
|
||||
<dt class="font-mono text-[11px] uppercase tracking-[0.12em] text-ink-faint">Plan</dt>
|
||||
<dd id="billing-plan" class="mt-1 font-display text-lg uppercase text-ink">
|
||||
{plan_label(@subscription)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-mono text-[11px] uppercase tracking-[0.12em] text-ink-faint">
|
||||
Status
|
||||
</dt>
|
||||
<dd id="billing-status" class="mt-1 font-display text-lg uppercase text-ink">
|
||||
{status_label(@subscription)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-mono text-[11px] uppercase tracking-[0.12em] text-ink-faint">
|
||||
Current period ends
|
||||
</dt>
|
||||
<dd id="billing-period-end" class="mt-1 font-mono text-sm text-ink">
|
||||
<%= if @subscription && @subscription.current_period_end do %>
|
||||
{Calendar.strftime(@subscription.current_period_end, "%Y-%m-%d %H:%M UTC")}
|
||||
<% else %>
|
||||
-
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<div
|
||||
:if={@subscription && @subscription.status == "past_due"}
|
||||
class="mt-4 border border-signal p-3 text-sm text-ink-muted"
|
||||
>
|
||||
Your last payment failed. Update your payment method from the billing
|
||||
portal to keep the retainer active.
|
||||
</div>
|
||||
|
||||
<div
|
||||
:if={@subscription && @subscription.status == "canceled"}
|
||||
class="mt-4 bg-panel p-3 text-sm text-ink-muted"
|
||||
>
|
||||
This retainer is canceled. Your account keeps full access to the record,
|
||||
alerts, and the API.
|
||||
</div>
|
||||
|
||||
<div class="mt-5 flex flex-wrap gap-3">
|
||||
<.button
|
||||
:if={@subscription && @subscription.stripe_customer_id}
|
||||
id="billing-manage-button"
|
||||
type="button"
|
||||
phx-click="manage"
|
||||
size="sm"
|
||||
>
|
||||
Manage subscription
|
||||
</.button>
|
||||
<.button
|
||||
:if={is_nil(@subscription) || @subscription.status in ["canceled", "incomplete"]}
|
||||
id="billing-subscribe-enterprise"
|
||||
type="button"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
phx-click="subscribe"
|
||||
phx-value-plan="enterprise"
|
||||
>
|
||||
Start managed disclosure
|
||||
</.button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p class="text-sm leading-6 text-ink-faint">
|
||||
See <.link navigate={~p"/pricing"} class="text-ink-muted underline hover:text-ink">
|
||||
pricing
|
||||
</.link>. Payments are processed by Stripe.
|
||||
</p>
|
||||
</Layouts.page>
|
||||
</Layouts.app>
|
||||
"""
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue