Current section
Files
Jump to
Current section
Files
lib/phoenix_kit_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={gettext("Subscriptions")}
subtitle={gettext("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" /> {gettext("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" /> {gettext("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">{gettext("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">{gettext("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">{gettext("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">{gettext("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">{gettext("Cancelled")}</div>
<div class="stat-value text-2xl text-error">{@stats.cancelled}</div>
</div>
</div>
<%!-- Subscriptions Table --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-0">
<%= 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">{gettext("No subscriptions found")}</p>
<p class="text-sm mt-2">
{gettext("Subscriptions will appear here when customers subscribe to plans")}
</p>
</div>
<% else %>
<.table_default
id="subscriptions-table"
variant="zebra"
class="w-full"
toggleable={true}
items={@subscriptions}
card_fields={
fn sub ->
[
%{
label: gettext("User"),
value: if(sub.user, do: sub.user.email, else: "-")
},
%{
label: gettext("Plan"),
value: if(sub.subscription_type, do: sub.subscription_type.name, else: "-")
},
%{label: gettext("Status"), value: sub.status},
%{
label: gettext("Period"),
value:
if(sub.current_period_end,
do: sub.current_period_end,
else: "-"
)
}
]
end
}
>
<:toolbar_title>
<div class="join">
<button
phx-click="filter_status"
phx-value-status="all"
class={["join-item btn btn-sm", @status_filter == "all" && "btn-active"]}
>
{gettext("All")}
</button>
<button
phx-click="filter_status"
phx-value-status="active"
class={["join-item btn btn-sm", @status_filter == "active" && "btn-active"]}
>
{gettext("Active")}
</button>
<button
phx-click="filter_status"
phx-value-status="trialing"
class={["join-item btn btn-sm", @status_filter == "trialing" && "btn-active"]}
>
{gettext("Trialing")}
</button>
<button
phx-click="filter_status"
phx-value-status="past_due"
class={["join-item btn btn-sm", @status_filter == "past_due" && "btn-active"]}
>
{gettext("Past Due")}
</button>
<button
phx-click="filter_status"
phx-value-status="cancelled"
class={["join-item btn btn-sm", @status_filter == "cancelled" && "btn-active"]}
>
{gettext("Cancelled")}
</button>
</div>
</:toolbar_title>
<:toolbar_actions>
<form phx-submit="search" class="w-full md:w-auto">
<div class="join w-full md:w-64">
<input
type="text"
name="search"
value={@search}
placeholder={gettext("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>
</:toolbar_actions>
<:card_header :let={sub}>
<div class="flex items-center justify-between">
<span class="font-medium">
{if sub.subscription_type, do: sub.subscription_type.name, else: gettext("Subscription")}
</span>
<span class={["badge badge-sm h-auto", status_badge_class(sub.status)]}>
{sub.status}
</span>
</div>
</:card_header>
<:card_actions :let={sub}>
<.link
navigate={
PhoenixKit.Utils.Routes.path("/admin/billing/subscriptions/#{sub.uuid}")
}
class="btn btn-xs btn-outline btn-info"
>
<.icon name="hero-eye" class="w-3 h-3" /> {gettext("View")}
</.link>
<%= if sub.status == "active" && !sub.cancel_at_period_end do %>
<button
phx-click="cancel_subscription"
phx-value-uuid={sub.uuid}
class="btn btn-xs btn-outline btn-error"
data-confirm={gettext("Are you sure you want to cancel this subscription?")}
>
{gettext("Cancel")}
</button>
<% end %>
</:card_actions>
<.table_default_header>
<.table_default_row>
<.table_default_header_cell>{gettext("Customer")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Subscription Type")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Status")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Current Period")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Price")}</.table_default_header_cell>
<.table_default_header_cell></.table_default_header_cell>
</.table_default_row>
</.table_default_header>
<.table_default_body>
<%= for sub <- @subscriptions do %>
<.table_default_row
class="hover cursor-pointer"
phx-click="view_subscription"
phx-value-uuid={sub.uuid}
>
<.table_default_cell>
<%= if sub.user do %>
<div class="flex items-center gap-2">
<.user_avatar user={sub.user} size="sm" />
<div>
<div class="font-medium text-sm">{sub.user.email}</div>
</div>
</div>
<% else %>
<span class="text-base-content/50">{gettext("No user")}</span>
<% end %>
</.table_default_cell>
<.table_default_cell>
<%= if sub.subscription_type do %>
<div>
<div class="font-medium">{sub.subscription_type.name}</div>
<div class="text-xs text-base-content/60">
{format_interval(
sub.subscription_type.interval,
sub.subscription_type.interval_count
)}
</div>
</div>
<% else %>
<span class="text-base-content/50">{gettext("No subscription type")}</span>
<% end %>
</.table_default_cell>
<.table_default_cell>
<span class={["badge badge-sm h-auto", status_badge_class(sub.status)]}>
{sub.status}
</span>
<%= if sub.cancel_at_period_end do %>
<span class="badge badge-warning badge-xs ml-1">{gettext("Cancels at end")}</span>
<% end %>
</.table_default_cell>
<.table_default_cell>
<div class="text-sm">
<%= if sub.current_period_start && sub.current_period_end do %>
<div>
<.time_ago datetime={sub.current_period_start} /> →
</div>
<div class="text-base-content/60">
<.time_ago datetime={sub.current_period_end} />
</div>
<% else %>
<span class="text-base-content/50">-</span>
<% end %>
</div>
</.table_default_cell>
<.table_default_cell>
<%= if sub.subscription_type do %>
<.currency_compact
amount={sub.subscription_type.price}
currency={sub.subscription_type.currency}
/>
<% else %>
<span class="text-base-content/50">-</span>
<% end %>
</.table_default_cell>
<.table_default_cell>
<.table_row_menu id={"menu-#{sub.uuid}"}>
<.table_row_menu_link
navigate={
PhoenixKit.Utils.Routes.path(
"/admin/billing/subscriptions/#{sub.uuid}"
)
}
icon="hero-eye"
label={gettext("View")}
/>
<.table_row_menu_link
navigate={
PhoenixKit.Utils.Routes.path(
"/admin/billing/subscriptions/#{sub.uuid}/edit"
)
}
icon="hero-pencil"
label={gettext("Edit")}
/>
<%= if sub.status == "active" && !sub.cancel_at_period_end do %>
<.table_row_menu_divider />
<.table_row_menu_button
phx-click="cancel_subscription"
phx-value-uuid={sub.uuid}
data-confirm={
gettext("Are you sure you want to cancel this subscription?")
}
icon="hero-x-circle"
label={gettext("Cancel")}
variant="error"
/>
<% end %>
</.table_row_menu>
</.table_default_cell>
</.table_default_row>
<% end %>
</.table_default_body>
</.table_default>
<% end %>
</div>
</div>
</div>