Packages

phoenix_kit

1.7.42
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
phoenix_kit lib modules billing web subscriptions.html.heex
Raw

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 --%>
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4">
<div class="flex items-center gap-4">
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/billing")}
class="btn btn-ghost btn-sm"
>
<.icon name="hero-arrow-left" class="w-4 h-4" />
</.link>
<div>
<h1 class="text-3xl font-bold">Subscriptions</h1>
<p class="text-base-content/60 mt-1">Manage recurring billing subscriptions</p>
</div>
</div>
<div class="flex gap-2">
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/billing/plans")}
class="btn btn-outline btn-sm"
>
<.icon name="hero-squares-2x2" class="w-4 h-4" /> Manage Plans
</.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>
</div>
</div>
<%!-- 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>Plan</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.plan do %>
<div>
<div class="font-medium">{subscription.plan.name}</div>
<div class="text-xs text-base-content/60">
{format_interval(
subscription.plan.interval,
subscription.plan.interval_count
)}
</div>
</div>
<% else %>
<span class="text-base-content/50">No plan</span>
<% end %>
</td>
<td>
<span class={["badge badge-sm", 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.plan do %>
<.currency_compact
amount={subscription.plan.price}
currency={subscription.plan.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-ghost btn-xs"
>
<.icon name="hero-eye" class="w-4 h-4" />
</.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-ghost btn-xs text-error"
data-confirm="Cancel this subscription at period end?"
>
<.icon name="hero-x-mark" class="w-4 h-4" />
</button>
<% end %>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
</div>
</div>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>