Current section

Files

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

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

<div class="container mx-auto px-4 py-6">
<%!-- Header --%>
<.admin_page_header
back={PhoenixKit.Utils.Routes.path("/admin/billing")}
title="Subscriptions"
subtitle="Manage recurring billing subscriptions"
>
<:actions>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/billing/subscription-types")}
class="btn btn-outline btn-sm"
>
<.icon name="hero-squares-2x2" class="w-4 h-4" /> Manage Subscription Types
</.link>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/billing/subscriptions/new")}
class="btn btn-primary btn-sm"
>
<.icon name="hero-plus" class="w-4 h-4" /> New Subscription
</.link>
</:actions>
</.admin_page_header>
<%!-- Stats Cards --%>
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6">
<div class="stat bg-base-100 rounded-box shadow">
<div class="stat-title">Total</div>
<div class="stat-value text-2xl">{@stats.total}</div>
</div>
<div class="stat bg-base-100 rounded-box shadow">
<div class="stat-title">Active</div>
<div class="stat-value text-2xl text-success">{@stats.active}</div>
</div>
<div class="stat bg-base-100 rounded-box shadow">
<div class="stat-title">Trialing</div>
<div class="stat-value text-2xl text-info">{@stats.trialing}</div>
</div>
<div class="stat bg-base-100 rounded-box shadow">
<div class="stat-title">Past Due</div>
<div class="stat-value text-2xl text-warning">{@stats.past_due}</div>
</div>
<div class="stat bg-base-100 rounded-box shadow">
<div class="stat-title">Cancelled</div>
<div class="stat-value text-2xl text-error">{@stats.cancelled}</div>
</div>
</div>
<%!-- Filters --%>
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body py-4">
<div class="flex flex-col md:flex-row gap-4 items-center">
<%!-- Status Filter --%>
<div class="join">
<button
phx-click="filter_status"
phx-value-status="all"
class={["join-item btn btn-sm", @status_filter == "all" && "btn-active"]}
>
All
</button>
<button
phx-click="filter_status"
phx-value-status="active"
class={["join-item btn btn-sm", @status_filter == "active" && "btn-active"]}
>
Active
</button>
<button
phx-click="filter_status"
phx-value-status="trialing"
class={["join-item btn btn-sm", @status_filter == "trialing" && "btn-active"]}
>
Trialing
</button>
<button
phx-click="filter_status"
phx-value-status="past_due"
class={["join-item btn btn-sm", @status_filter == "past_due" && "btn-active"]}
>
Past Due
</button>
<button
phx-click="filter_status"
phx-value-status="cancelled"
class={["join-item btn btn-sm", @status_filter == "cancelled" && "btn-active"]}
>
Cancelled
</button>
</div>
<%!-- Search --%>
<form phx-submit="search" class="flex-1 md:max-w-xs">
<div class="join w-full">
<input
type="text"
name="search"
value={@search}
placeholder="Search by email..."
class="input input-bordered input-sm join-item flex-1"
/>
<button type="submit" class="btn btn-sm join-item">
<.icon name="hero-magnifying-glass" class="w-4 h-4" />
</button>
</div>
</form>
</div>
</div>
</div>
<%!-- Subscriptions Table --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<%= if Enum.empty?(@subscriptions) do %>
<div class="text-center py-12 text-base-content/50">
<.icon name="hero-credit-card" class="w-12 h-12 mx-auto mb-4 opacity-50" />
<p class="text-lg">No subscriptions found</p>
<p class="text-sm mt-2">
Subscriptions will appear here when customers subscribe to plans
</p>
</div>
<% else %>
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>Customer</th>
<th>Subscription Type</th>
<th>Status</th>
<th>Current Period</th>
<th>Price</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<%= for subscription <- @subscriptions do %>
<tr>
<td>
<%= if subscription.user do %>
<div class="flex items-center gap-3">
<.user_avatar user={subscription.user} size="sm" />
<div>
<div class="font-medium text-sm">{subscription.user.email}</div>
<div class="text-xs text-base-content/60">
ID: {subscription.uuid}
</div>
</div>
</div>
<% else %>
<span class="text-base-content/50">No user</span>
<% end %>
</td>
<td>
<%= if subscription.subscription_type do %>
<div>
<div class="font-medium">{subscription.subscription_type.name}</div>
<div class="text-xs text-base-content/60">
{format_interval(
subscription.subscription_type.interval,
subscription.subscription_type.interval_count
)}
</div>
</div>
<% else %>
<span class="text-base-content/50">No subscription type</span>
<% end %>
</td>
<td>
<span class={[
"badge badge-sm h-auto",
status_badge_class(subscription.status)
]}>
{subscription.status}
</span>
<%= if subscription.cancel_at_period_end do %>
<span class="badge badge-warning badge-xs ml-1">Cancels at end</span>
<% end %>
</td>
<td>
<div class="text-sm">
<%= if subscription.current_period_start && subscription.current_period_end do %>
<div>
<.time_ago datetime={subscription.current_period_start} />
</div>
<div class="text-base-content/60">
<.time_ago datetime={subscription.current_period_end} />
</div>
<% else %>
<span class="text-base-content/50">-</span>
<% end %>
</div>
</td>
<td>
<%= if subscription.subscription_type do %>
<.currency_compact
amount={subscription.subscription_type.price}
currency={subscription.subscription_type.currency}
/>
<% else %>
<span class="text-base-content/50">-</span>
<% end %>
</td>
<td class="text-right">
<div class="flex gap-1 justify-end">
<.link
navigate={
PhoenixKit.Utils.Routes.path(
"/admin/billing/subscriptions/#{subscription.uuid}"
)
}
class="btn btn-xs btn-outline btn-info tooltip tooltip-bottom"
data-tip={gettext("View")}
>
<.icon name="hero-eye" class="w-4 h-4 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("View")}</span>
</.link>
<%= if subscription.status in ["active", "trialing"] && !subscription.cancel_at_period_end do %>
<button
phx-click="cancel_subscription"
phx-value-uuid={subscription.uuid}
class="btn btn-xs btn-outline btn-error tooltip tooltip-bottom"
data-confirm="Cancel this subscription at period end?"
data-tip={gettext("Cancel")}
>
<.icon name="hero-x-mark" class="w-4 h-4 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("Cancel")}</span>
</button>
<% end %>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
</div>
</div>
</div>