Packages
phoenix_kit
1.7.49
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_plan_form.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="{@project_title} {@page_title}"
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/plans")}>
<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 @mode == :new do %>
Create a new subscription plan
<% else %>
Edit plan details and pricing
<% end %>
</p>
</.admin_page_header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<%!-- Form --%>
<div class="lg:col-span-2">
<.form for={@form} phx-change="validate" phx-submit="save">
<%!-- Basic Info --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">Basic Information</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div class="form-control">
<label class="label">
<span class="label-text">Plan Name <span class="text-error">*</span></span>
</label>
<input
type="text"
name="subscription_plan[name]"
value={@form[:name].value}
class={["input input-bordered", @form[:name].errors != [] && "input-error"]}
placeholder="Pro Plan"
required
/>
<%= if @form[:name].errors != [] do %>
<label class="label">
<span class="label-text-alt text-error">
{error_to_string(@form[:name].errors)}
</span>
</label>
<% end %>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Slug <span class="text-error">*</span></span>
</label>
<input
type="text"
name="subscription_plan[slug]"
value={@form[:slug].value}
class={[
"input input-bordered font-mono",
@form[:slug].errors != [] && "input-error"
]}
placeholder="pro-monthly"
required
/>
<%= if @form[:slug].errors != [] do %>
<label class="label">
<span class="label-text-alt text-error">
{error_to_string(@form[:slug].errors)}
</span>
</label>
<% else %>
<label class="label">
<span class="label-text-alt">URL-friendly unique identifier</span>
</label>
<% end %>
</div>
</div>
<div class="form-control mt-4">
<label class="label">
<span class="label-text">Description</span>
</label>
<textarea
name="subscription_plan[description]"
class="textarea textarea-bordered"
rows="2"
placeholder="A brief description of this plan"
>{@form[:description].value}</textarea>
</div>
</div>
</div>
<%!-- Pricing --%>
<div class="card bg-base-100 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Pricing</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4">
<div class="form-control">
<label class="label">
<span class="label-text">Price <span class="text-error">*</span></span>
</label>
<input
type="number"
name="subscription_plan[price]"
value={@form[:price].value}
class={["input input-bordered", @form[:price].errors != [] && "input-error"]}
step="0.01"
min="0"
placeholder="9.99"
required
/>
<%= if @form[:price].errors != [] do %>
<label class="label">
<span class="label-text-alt text-error">
{error_to_string(@form[:price].errors)}
</span>
</label>
<% end %>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Currency</span>
</label>
<select name="subscription_plan[currency]" class="select select-bordered">
<option value="EUR" selected={@form[:currency].value == "EUR"}>EUR</option>
<option value="USD" selected={@form[:currency].value == "USD"}>USD</option>
<option value="GBP" selected={@form[:currency].value == "GBP"}>GBP</option>
<option value="INR" selected={@form[:currency].value == "INR"}>INR</option>
</select>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Trial Days</span>
</label>
<input
type="number"
name="subscription_plan[trial_days]"
value={@form[:trial_days].value}
class="input input-bordered"
min="0"
placeholder="0"
/>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div class="form-control">
<label class="label">
<span class="label-text">Billing Interval</span>
</label>
<select name="subscription_plan[interval]" class="select select-bordered">
<option value="day" selected={@form[:interval].value == "day"}>Day</option>
<option value="week" selected={@form[:interval].value == "week"}>Week</option>
<option value="month" selected={@form[:interval].value == "month"}>
Month
</option>
<option value="year" selected={@form[:interval].value == "year"}>Year</option>
</select>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Interval Count</span>
</label>
<input
type="number"
name="subscription_plan[interval_count]"
value={@form[:interval_count].value || 1}
class="input input-bordered"
min="1"
max="12"
placeholder="1"
/>
<label class="label">
<span class="label-text-alt">
e.g. 3 months = interval "month", count 3
</span>
</label>
</div>
</div>
</div>
</div>
<%!-- Features --%>
<div class="card bg-base-100 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Features</h2>
<p class="text-sm text-base-content/60">
List the features included in this plan (one per line)
</p>
<div class="form-control mt-4">
<textarea
name="features"
phx-change="update_features"
phx-debounce="300"
class="textarea textarea-bordered font-mono"
rows="6"
placeholder="Unlimited users Priority support Advanced analytics Custom integrations"
>{@features_input}</textarea>
</div>
</div>
</div>
<%!-- Settings --%>
<div class="card bg-base-100 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Settings</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div class="form-control">
<label class="label">
<span class="label-text">Sort Order</span>
</label>
<input
type="number"
name="subscription_plan[sort_order]"
value={@form[:sort_order].value}
class="input input-bordered"
min="0"
placeholder="0"
/>
<label class="label">
<span class="label-text-alt">Lower numbers appear first</span>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input
type="checkbox"
name="subscription_plan[active]"
value="true"
checked={@form[:active].value == true}
class="checkbox checkbox-primary"
/>
<span class="label-text">Active</span>
</label>
<label class="label">
<span class="label-text-alt">
Only active plans are shown to customers
</span>
</label>
</div>
</div>
</div>
</div>
<%!-- Actions --%>
<div class="flex justify-end gap-4 mt-6">
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/billing/plans")}
class="btn btn-ghost"
>
Cancel
</.link>
<button type="submit" class="btn btn-primary">
<.icon name="hero-check" class="w-4 h-4" />
{if @mode == :new, do: "Create Plan", else: "Update Plan"}
</button>
</div>
</.form>
</div>
<%!-- Preview --%>
<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">Preview</h2>
<div class="divider my-2"></div>
<h3 class="text-xl font-bold">{@form[:name].value || "Plan Name"}</h3>
<p class="text-sm text-base-content/60">
{@form[:description].value || "Plan description"}
</p>
<div class="mt-4">
<div class="text-3xl font-bold">
<%= if @form[:price].value do %>
<.currency_amount
amount={@form[:price].value}
currency={@form[:currency].value || "EUR"}
/>
<% else %>
<span class="text-base-content/30">$0.00</span>
<% end %>
</div>
<div class="text-sm text-base-content/60">
<% interval = @form[:interval].value || "month" %>
<% count = @form[:interval_count].value || 1 %> per {if count == 1,
do: interval,
else: "#{count} #{interval}s"}
</div>
</div>
<%= if @form[:trial_days].value && @form[:trial_days].value != "" && @form[:trial_days].value != "0" do %>
<div class="badge badge-info badge-outline mt-2">
{@form[:trial_days].value} day trial
</div>
<% end %>
<% features =
@features_input
|> String.split("\n")
|> Enum.map(&String.trim/1)
|> Enum.reject(&(&1 == "")) %>
<%= if length(features) > 0 do %>
<div class="divider my-2"></div>
<ul class="space-y-1">
<%= for feature <- features do %>
<li class="flex items-center gap-2 text-sm">
<.icon name="hero-check" class="w-4 h-4 text-success flex-shrink-0" />
<span>{feature}</span>
</li>
<% end %>
</ul>
<% end %>
</div>
</div>
</div>
</div>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>