Current section

Files

Jump to
phoenix_kit_billing lib phoenix_kit modules billing web subscription_detail.html.heex
Raw

lib/phoenix_kit/modules/billing/web/subscription_detail.html.heex

<div class="container mx-auto px-4 py-6">
<%!-- Header --%>
<.admin_page_header back={PhoenixKit.Utils.Routes.path("/admin/billing/subscriptions")}>
<div class="flex items-center gap-3">
<h1 class="text-xl sm:text-2xl lg:text-3xl font-bold text-base-content">
Subscription #{@subscription.uuid}
</h1>
<span class={["badge badge-lg h-auto", status_badge_class(@subscription.status)]}>
{@subscription.status}
</span>
</div>
<p class="text-sm text-base-content/60 mt-0.5">
Created <.time_ago datetime={@subscription.inserted_at} />
</p>
<:actions>
<%!-- Change Subscription Type Button (for active/trialing subscriptions) --%>
<%= if @subscription.status in ["active", "trialing"] && !@subscription.cancel_at_period_end do %>
<button phx-click="open_change_subscription_type_modal" class="btn btn-outline btn-sm">
<.icon name="hero-arrow-path" class="w-4 h-4" /> Change Subscription Type
</button>
<% end %>
<%!-- Action Buttons based on status --%>
<%= case @subscription.status do %>
<% "active" -> %>
<%= if @subscription.cancel_at_period_end do %>
<button phx-click="resume" class="btn btn-success btn-sm">
<.icon name="hero-play" class="w-4 h-4" /> Resume
</button>
<% else %>
<button phx-click="pause" class="btn btn-warning btn-sm">
<.icon name="hero-pause" class="w-4 h-4" /> Pause
</button>
<button
phx-click="cancel_at_period_end"
class="btn btn-error btn-outline btn-sm"
data-confirm="Cancel subscription at period end?"
>
<.icon name="hero-x-mark" class="w-4 h-4" /> Cancel at Period End
</button>
<% end %>
<% "trialing" -> %>
<button
phx-click="cancel_now"
class="btn btn-error btn-sm"
data-confirm="Cancel subscription immediately?"
>
<.icon name="hero-x-mark" class="w-4 h-4" /> Cancel
</button>
<% "paused" -> %>
<button phx-click="resume" class="btn btn-success btn-sm">
<.icon name="hero-play" class="w-4 h-4" /> Resume
</button>
<button
phx-click="cancel_now"
class="btn btn-error btn-outline btn-sm"
data-confirm="Cancel subscription?"
>
<.icon name="hero-x-mark" class="w-4 h-4" /> Cancel
</button>
<% "past_due" -> %>
<button
phx-click="cancel_now"
class="btn btn-error btn-sm"
data-confirm="Cancel subscription immediately?"
>
<.icon name="hero-x-mark" class="w-4 h-4" /> Cancel Now
</button>
<% _ -> %>
<% end %>
</:actions>
</.admin_page_header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<%!-- Main Content --%>
<div class="lg:col-span-2 space-y-6">
<%!-- Subscription Type Details --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">Subscription Type Details</h2>
<%= if @subscription.subscription_type do %>
<div class="mt-4">
<div class="flex items-center justify-between">
<div>
<h3 class="text-xl font-bold">{@subscription.subscription_type.name}</h3>
<p class="text-base-content/60">
{@subscription.subscription_type.description}
</p>
</div>
<div class="text-right">
<div class="text-2xl font-bold">
<.currency_amount
amount={@subscription.subscription_type.price}
currency={@subscription.subscription_type.currency}
/>
</div>
<div class="text-sm text-base-content/60">
{format_interval(
@subscription.subscription_type.interval,
@subscription.subscription_type.interval_count
)}
</div>
</div>
</div>
<%!-- Features --%>
<%= if @subscription.subscription_type.features && is_list(@subscription.subscription_type.features) && length(@subscription.subscription_type.features) > 0 do %>
<div class="divider">Features</div>
<ul class="space-y-2">
<%= for feature <- @subscription.subscription_type.features do %>
<li class="flex items-center gap-2">
<.icon name="hero-check" class="w-4 h-4 text-success" />
<span>{feature}</span>
</li>
<% end %>
</ul>
<% end %>
</div>
<% else %>
<div class="text-center py-6 text-base-content/50">
<p>No subscription type associated</p>
</div>
<% end %>
</div>
</div>
<%!-- Billing Period --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">Billing Period</h2>
<div class="grid grid-cols-2 gap-4 mt-4">
<div class="bg-base-200 rounded-lg p-4">
<div class="text-sm text-base-content/60">Period Start</div>
<div class="font-medium">
<%= if @subscription.current_period_start do %>
<.time_ago datetime={@subscription.current_period_start} />
<% else %>
<span class="text-base-content/50">Not set</span>
<% end %>
</div>
</div>
<div class="bg-base-200 rounded-lg p-4">
<div class="text-sm text-base-content/60">Period End</div>
<div class="font-medium">
<%= if @subscription.current_period_end do %>
<.time_ago datetime={@subscription.current_period_end} />
<% days = days_until_renewal(@subscription) %>
<%= if days && days > 0 do %>
<span class="text-xs text-base-content/60 block">
({days} days remaining)
</span>
<% end %>
<% else %>
<span class="text-base-content/50">Not set</span>
<% end %>
</div>
</div>
</div>
<%!-- Trial Period --%>
<%= if @subscription.trial_start || @subscription.trial_end do %>
<div class="divider">Trial Period</div>
<div class="grid grid-cols-2 gap-4">
<div class="bg-info/10 rounded-lg p-4">
<div class="text-sm text-info">Trial Start</div>
<div class="font-medium">
<%= if @subscription.trial_start do %>
<.time_ago datetime={@subscription.trial_start} />
<% else %>
<span class="text-base-content/50">-</span>
<% end %>
</div>
</div>
<div class="bg-info/10 rounded-lg p-4">
<div class="text-sm text-info">Trial End</div>
<div class="font-medium">
<%= if @subscription.trial_end do %>
<.time_ago datetime={@subscription.trial_end} />
<% else %>
<span class="text-base-content/50">-</span>
<% end %>
</div>
</div>
</div>
<% end %>
<%!-- Grace Period (if past_due) --%>
<%= if @subscription.status == "past_due" && @subscription.grace_period_end do %>
<div class="divider">Grace Period</div>
<div class="alert alert-warning">
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
<div>
<div class="font-medium">Payment Failed - Grace Period Active</div>
<div class="text-sm">
Grace period ends <.time_ago datetime={@subscription.grace_period_end} />
<% grace_days = grace_period_remaining(@subscription) %>
<%= if grace_days && grace_days > 0 do %>
({grace_days} days remaining)
<% end %>
</div>
<div class="text-sm mt-1">
Renewal attempts: {@subscription.renewal_attempts || 0}
</div>
</div>
</div>
<% end %>
</div>
</div>
<%!-- Cancellation Info --%>
<%= if @subscription.cancel_at_period_end || @subscription.cancelled_at do %>
<div class="card bg-error/10 border border-error shadow-xl">
<div class="card-body">
<h2 class="card-title text-error">
<.icon name="hero-x-circle" class="w-6 h-6" /> Cancellation
</h2>
<%= if @subscription.cancel_at_period_end do %>
<p>This subscription will cancel at the end of the current billing period.</p>
<% end %>
<%= if @subscription.cancelled_at do %>
<p>
Cancelled <.time_ago datetime={@subscription.cancelled_at} />
</p>
<% end %>
</div>
</div>
<% end %>
</div>
<%!-- Sidebar --%>
<div class="space-y-6">
<%!-- Customer Info --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">Customer</h2>
<%= if @subscription.user do %>
<div class="flex items-center gap-3 mt-2">
<.user_avatar user={@subscription.user} size="lg" />
<div>
<div class="font-medium">{@subscription.user.email}</div>
<.link
navigate={
PhoenixKit.Utils.Routes.path("/admin/users/edit/#{@subscription.user.uuid}")
}
class="text-sm text-primary hover:underline"
>
View Profile
</.link>
</div>
</div>
<% else %>
<p class="text-base-content/50 mt-2">No customer linked</p>
<% end %>
</div>
</div>
<%!-- Payment Method --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">Payment Method</h2>
<%= if @subscription.payment_method do %>
<div class="mt-2">
<div class="flex items-center gap-3 p-3 bg-base-200 rounded-lg">
<.icon name="hero-credit-card" class="w-6 h-6 text-primary" />
<div>
<div class="font-medium">
{PhoenixKit.Modules.Billing.PaymentMethod.display_name(
@subscription.payment_method
)}
</div>
<div class="text-sm text-base-content/60">
Provider: {@subscription.payment_method.provider}
</div>
</div>
</div>
</div>
<% else %>
<div class="text-center py-4 text-base-content/50">
<.icon name="hero-credit-card" class="w-8 h-8 mx-auto mb-2 opacity-50" />
<p>No payment method</p>
</div>
<% end %>
</div>
</div>
<%!-- Quick Info --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">Quick Info</h2>
<div class="space-y-3 mt-2">
<div class="flex justify-between items-center">
<span class="text-base-content/60">Status</span>
<span class={["badge", status_badge_class(@subscription.status)]}>
{@subscription.status}
</span>
</div>
<div class="flex justify-between items-center">
<span class="text-base-content/60">Created</span>
<span><.time_ago datetime={@subscription.inserted_at} /></span>
</div>
<%= if @subscription.started_at do %>
<div class="flex justify-between items-center">
<span class="text-base-content/60">Started</span>
<span><.time_ago datetime={@subscription.started_at} /></span>
</div>
<% end %>
<%= if @subscription.ended_at do %>
<div class="flex justify-between items-center">
<span class="text-base-content/60">Ended</span>
<span><.time_ago datetime={@subscription.ended_at} /></span>
</div>
<% end %>
</div>
</div>
</div>
</div>
</div>
</div>
<%!-- Change Subscription Type Modal --%>
<%= if @show_change_subscription_type_modal do %>
<div class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg">Change Subscription Type</h3>
<%!-- Current Subscription Type --%>
<%= if @subscription.subscription_type do %>
<div class="mt-4 p-3 bg-base-200 rounded-lg">
<div class="text-sm text-base-content/60">Current Subscription Type</div>
<div class="font-medium">{@subscription.subscription_type.name}</div>
<div class="text-sm">
<.currency_compact
amount={@subscription.subscription_type.price}
currency={@subscription.subscription_type.currency}
/>
/ {format_interval(
@subscription.subscription_type.interval,
@subscription.subscription_type.interval_count
)}
</div>
</div>
<% end %>
<%!-- New Subscription Type Selection --%>
<div class="form-control mt-4">
<label class="label">
<span class="label-text">Select New Subscription Type</span>
</label>
<select
name="subscription_type_uuid"
phx-change="select_new_subscription_type"
class="select select-bordered w-full"
>
<option value="">Choose a subscription type...</option>
<%= for type <- @subscription_types do %>
<%= if type.uuid != @subscription.subscription_type_uuid do %>
<option
value={type.uuid}
selected={
to_string(@selected_new_subscription_type_uuid) == to_string(type.uuid)
}
>
{type.name} - <.currency_compact amount={type.price} currency={type.currency} />
/ {format_interval(type.interval, type.interval_count)}
</option>
<% end %>
<% end %>
</select>
</div>
<%!-- Warning --%>
<div class="alert alert-warning mt-4">
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
<span>Plan change takes effect immediately. The billing period will be adjusted.</span>
</div>
<%!-- Modal Actions --%>
<div class="modal-action">
<button type="button" phx-click="close_change_subscription_type_modal" class="btn">
Cancel
</button>
<button
type="button"
phx-click="change_subscription_type"
class="btn btn-primary"
disabled={is_nil(@selected_new_subscription_type_uuid)}
>
<.icon name="hero-arrow-path" class="w-4 h-4" /> Change Subscription Type
</button>
</div>
</div>
<div class="modal-backdrop" phx-click="close_change_subscription_type_modal"></div>
</div>
<% end %>