Packages
phoenix_kit
1.7.65
1.7.208
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/subscription_detail.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="{@project_title} Subscription #{@subscription.uuid}"
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/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 %>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>