Packages
phoenix_kit
1.7.81
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/billing/web/subscriptions.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="{@project_title} Subscriptions"
current_path={@url_path}
project_title={@project_title}
>
<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>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>