Current section
Files
Jump to
Current section
Files
lib/phoenix_kit_billing/web/subscription_form.html.heex
<div class="container mx-auto px-4 py-6">
<%!-- Header --%>
<.admin_page_header back={PhoenixKit.Utils.Routes.path("/admin/billing/subscriptions")}>
<h1 class="text-xl sm:text-2xl lg:text-3xl font-bold text-base-content">{@page_title}</h1>
<p class="text-sm text-base-content/60 mt-0.5">
<%= if @live_action == :edit do %>
{gettext("Edit subscription settings")}
<% else %>
{gettext("Manually create a subscription for a customer")}
<% end %>
</p>
</.admin_page_header>
<%!-- Error Alert --%>
<%= if @error do %>
<div class="alert alert-error mb-6">
<.icon name="hero-exclamation-circle" class="w-5 h-5" />
<span>{@error}</span>
<button type="button" phx-click="clear_error" class="btn btn-ghost btn-sm btn-circle">
<.icon name="hero-x-mark" class="w-4 h-4" />
</button>
</div>
<% end %>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<%!-- Form --%>
<div class="lg:col-span-2 space-y-6">
<%!-- Customer Section --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">
<.icon name="hero-user" class="w-5 h-5" /> {gettext("Customer")}
</h2>
<%= if @live_action == :edit do %>
<%!-- Edit mode: read-only user display --%>
<div class="flex items-center gap-3 p-4 bg-base-200 rounded-lg mt-4">
<.user_avatar user={@selected_user} size="md" />
<div>
<div class="font-medium">{@selected_user.email}</div>
<div class="text-sm text-base-content/60">{gettext("ID:")} {@selected_user.uuid}</div>
</div>
</div>
<% else %>
<%!-- Create mode: user search --%>
<%= if @selected_user do %>
<div class="flex items-center justify-between p-4 bg-base-200 rounded-lg mt-4">
<div class="flex items-center gap-3">
<.user_avatar user={@selected_user} size="md" />
<div>
<div class="font-medium">{@selected_user.email}</div>
<div class="text-sm text-base-content/60">{gettext("ID:")} {@selected_user.uuid}</div>
</div>
</div>
<button type="button" phx-click="clear_user" class="btn btn-ghost btn-sm">
<.icon name="hero-x-mark" class="w-4 h-4" /> {gettext("Change")}
</button>
</div>
<% else %>
<div class="form-control mt-4">
<label class="label">
<span class="label-text">
{gettext("Search by email")} <span class="text-error">*</span>
</span>
</label>
<div class="relative">
<form phx-change="search_user" phx-submit="search_user">
<input
type="text"
value={@user_search}
phx-debounce="300"
name="query"
class="input input-bordered w-full"
placeholder={gettext("Enter email address...")}
autocomplete="off"
/>
</form>
<%= if @user_search != "" && length(@user_results) > 0 do %>
<div class="absolute z-10 w-full mt-1 bg-base-100 border border-base-300 rounded-lg shadow-lg max-h-60 overflow-y-auto">
<%= for user <- @user_results do %>
<button
type="button"
phx-click="select_user"
phx-value-id={user.uuid}
class="w-full px-4 py-3 text-left hover:bg-base-200 flex items-center gap-3 border-b border-base-200 last:border-b-0"
>
<.user_avatar user={user} size="sm" />
<div>
<div class="font-medium">{user.email}</div>
<div class="text-xs text-base-content/60">{gettext("ID:")} {user.uuid}</div>
</div>
</button>
<% end %>
</div>
<% end %>
<%= if @user_search != "" && length(@user_results) == 0 do %>
<div class="absolute z-10 w-full mt-1 bg-base-100 border border-base-300 rounded-lg shadow-lg p-4 text-center text-base-content/60">
{gettext("No users found matching \"%{query}\"", query: @user_search)}
</div>
<% end %>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
<%!-- Plan Selection --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">
<.icon name="hero-squares-2x2" class="w-5 h-5" /> {gettext("Subscription Type")}
</h2>
<div class="form-control mt-4">
<label class="label">
<span class="label-text">
<%= if @live_action == :edit do %>
{gettext("Change subscription type")}
<% else %>
{gettext("Select a subscription type")} <span class="text-error">*</span>
<% end %>
</span>
</label>
<form phx-change="select_subscription_type">
<select
name="subscription_type_uuid"
class="select select-bordered w-full"
>
<option value="">{gettext("Choose a subscription type...")}</option>
<%= for type <- @subscription_types do %>
<option
value={type.uuid}
selected={to_string(@selected_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 %>
</select>
</form>
</div>
<%!-- Trial Period (create mode only) --%>
<%= if @live_action != :edit do %>
<div class="form-control mt-4">
<label class="label cursor-pointer justify-start gap-3">
<input
type="checkbox"
name="enable"
value="true"
checked={@enable_trial}
phx-click="toggle_trial"
phx-value-enable={if @enable_trial, do: "false", else: "true"}
class="checkbox checkbox-primary"
/>
<span class="label-text">{gettext("Start with trial period")}</span>
</label>
</div>
<%= if @enable_trial do %>
<div class="form-control mt-2">
<label class="label">
<span class="label-text">{gettext("Trial days")}</span>
</label>
<input
type="number"
name="days"
value={@trial_days}
phx-keyup="update_trial_days"
phx-debounce="300"
class="input input-bordered w-32"
min="1"
max="365"
placeholder="14"
/>
</div>
<% end %>
<% end %>
</div>
</div>
<%!-- Status & Period (edit mode only) --%>
<%= if @live_action == :edit do %>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">
<.icon name="hero-signal" class="w-5 h-5" /> {gettext("Status & Period")}
</h2>
<div class="flex flex-wrap items-center gap-3 mt-4">
<span class={["badge badge-lg h-auto", status_badge_class(@subscription.status)]}>
{@subscription.status}
</span>
<%!-- Status action buttons --%>
<%= case @subscription.status do %>
<% "active" -> %>
<button
type="button"
phx-click="pause_subscription"
data-confirm={gettext("Pause this subscription? The customer will lose access.")}
class="btn btn-warning btn-sm"
>
<.icon name="hero-pause" class="w-4 h-4" /> {gettext("Pause")}
</button>
<button
type="button"
phx-click="cancel_subscription"
data-confirm={gettext("Cancel this subscription? It will end at the current period.")}
class="btn btn-error btn-sm"
>
<.icon name="hero-x-circle" class="w-4 h-4" /> {gettext("Cancel")}
</button>
<% "paused" -> %>
<button
type="button"
phx-click="resume_subscription"
class="btn btn-success btn-sm"
>
<.icon name="hero-play" class="w-4 h-4" /> {gettext("Resume")}
</button>
<button
type="button"
phx-click="cancel_subscription"
data-confirm={gettext("Cancel this subscription? It will end at the current period.")}
class="btn btn-error btn-sm"
>
<.icon name="hero-x-circle" class="w-4 h-4" /> {gettext("Cancel")}
</button>
<% "trialing" -> %>
<button
type="button"
phx-click="cancel_subscription"
data-confirm={gettext("Cancel this subscription? The trial will end immediately.")}
class="btn btn-error btn-sm"
>
<.icon name="hero-x-circle" class="w-4 h-4" /> {gettext("Cancel")}
</button>
<% "past_due" -> %>
<div class="alert alert-warning py-2 px-3 text-sm">
<.icon name="hero-exclamation-triangle" class="w-4 h-4" />
{gettext("Payment overdue — renewal will be retried automatically")}
</div>
<% _ -> %>
<% end %>
</div>
<%!-- Period dates --%>
<div class="grid grid-cols-2 gap-4 mt-4">
<div>
<div class="text-sm text-base-content/60">{gettext("Period Start")}</div>
<div class="font-medium">
<%= if @subscription.current_period_start do %>
<.time_ago datetime={@subscription.current_period_start} />
<% else %>
—
<% end %>
</div>
</div>
<div>
<div class="text-sm text-base-content/60">{gettext("Period End")}</div>
<div class="font-medium">
<%= if @subscription.current_period_end do %>
<.time_ago datetime={@subscription.current_period_end} />
<% else %>
—
<% end %>
</div>
</div>
</div>
<%!-- Extend button (not for cancelled) --%>
<%= if @subscription.status != "cancelled" do %>
<div class="mt-4">
<button
type="button"
phx-click="extend_subscription"
class="btn btn-outline btn-sm"
>
<.icon name="hero-arrow-path" class="w-4 h-4" /> {gettext("Extend 30 days")}
</button>
</div>
<% end %>
</div>
</div>
<% end %>
<%!-- Payment Method (if user selected) --%>
<%= if @selected_user && length(@payment_methods) > 0 do %>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">
<.icon name="hero-credit-card" class="w-5 h-5" /> {gettext("Payment Method")}
<span class="badge badge-ghost">{gettext("Optional")}</span>
</h2>
<div class="form-control mt-4">
<label class="label">
<span class="label-text">{gettext("Select a saved payment method")}</span>
</label>
<form phx-change="select_payment_method">
<select
name="payment_method_uuid"
class="select select-bordered w-full"
>
<option value="">{gettext("No payment method")}</option>
<%= for pm <- @payment_methods do %>
<option
value={pm.uuid}
selected={to_string(@selected_payment_method_uuid) == to_string(pm.uuid)}
>
{format_payment_method(pm)}
<%= if pm.is_default do %>
{gettext("(Default)")}
<% end %>
</option>
<% end %>
</select>
</form>
<label class="label">
<span class="label-text-alt text-base-content/60">
{gettext("Payment method is optional for manually created subscriptions")}
</span>
</label>
</div>
</div>
</div>
<% end %>
<%!-- Actions --%>
<div class="flex justify-end gap-4">
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/billing/subscriptions")}
class="btn btn-ghost"
>
{gettext("Cancel")}
</.link>
<%= if @live_action == :edit do %>
<button
type="button"
phx-click="save"
class="btn btn-primary"
disabled={@subscription.status == "cancelled"}
>
<.icon name="hero-check" class="w-4 h-4" /> {gettext("Save Changes")}
</button>
<% else %>
<button
type="button"
phx-click="save"
class="btn btn-primary"
disabled={is_nil(@selected_user) || is_nil(@selected_subscription_type_uuid)}
>
<.icon name="hero-plus" class="w-4 h-4" /> {gettext("Create Subscription")}
</button>
<% end %>
</div>
</div>
<%!-- Summary Sidebar --%>
<div class="lg:col-span-1">
<div class="card bg-base-100 shadow-xl sticky top-6">
<div class="card-body">
<h2 class="card-title">{gettext("Summary")}</h2>
<div class="divider my-2"></div>
<%!-- Customer --%>
<div class="space-y-1">
<div class="text-sm text-base-content/60">{gettext("Customer")}</div>
<%= if @selected_user do %>
<div class="font-medium">{@selected_user.email}</div>
<% else %>
<div class="text-base-content/40">{gettext("Not selected")}</div>
<% end %>
</div>
<div class="divider my-2"></div>
<%!-- Subscription Type --%>
<div class="space-y-1">
<div class="text-sm text-base-content/60">{gettext("Subscription Type")}</div>
<%= if @selected_subscription_type_uuid do %>
<% type =
Enum.find(
@subscription_types,
&(to_string(&1.uuid) == to_string(@selected_subscription_type_uuid))
) %>
<%= if type do %>
<div class="font-medium">{type.name}</div>
<div class="text-2xl font-bold mt-1">
<.currency_amount amount={type.price} currency={type.currency} />
</div>
<div class="text-sm text-base-content/60">
{format_interval(type.interval, type.interval_count)}
</div>
<% end %>
<% else %>
<div class="text-base-content/40">{gettext("Not selected")}</div>
<% end %>
</div>
<%= if @live_action != :edit && @enable_trial && @trial_days != "" do %>
<div class="divider my-2"></div>
<div class="space-y-1">
<div class="text-sm text-base-content/60">{gettext("Trial Period")}</div>
<div class="badge badge-info">{gettext("%{count} days", count: @trial_days)}</div>
</div>
<% end %>
<%= if @live_action != :edit && @selected_payment_method_uuid do %>
<div class="divider my-2"></div>
<div class="space-y-1">
<div class="text-sm text-base-content/60">{gettext("Payment Method")}</div>
<% pm =
Enum.find(
@payment_methods,
&(to_string(&1.uuid) == to_string(@selected_payment_method_uuid))
) %>
<%= if pm do %>
<div class="font-medium">{format_payment_method(pm)}</div>
<% end %>
</div>
<% end %>
<div class="divider my-2"></div>
<%!-- Status --%>
<div class="space-y-1">
<div class="text-sm text-base-content/60">{gettext("Status")}</div>
<%= if @live_action == :edit && @subscription do %>
<span class={["badge", status_badge_class(@subscription.status)]}>
{@subscription.status}
</span>
<%= if @subscription.plan_name do %>
<div class="text-sm text-base-content/60 mt-2">{@subscription.plan_name}</div>
<% end %>
<%= if @subscription.price do %>
<div class="font-medium">
<.currency_amount
amount={@subscription.price}
currency={@subscription.currency}
/>
</div>
<% end %>
<% else %>
<%= if @enable_trial && @trial_days != "" do %>
<div class="badge badge-info">{gettext("Trialing")}</div>
<% else %>
<div class="badge badge-success">{gettext("Active")}</div>
<% end %>
<% end %>
</div>
</div>
</div>
</div>
</div>
</div>