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/currencies.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="{@project_title} Currencies"
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")}
title="Currencies"
subtitle="Manage supported currencies for billing"
>
<:actions>
<button phx-click="show_add_form" class="btn btn-primary btn-sm">
<.icon name="hero-plus" class="w-4 h-4" /> Add Currency
</button>
<button phx-click="show_import" class="btn btn-ghost btn-sm">
<.icon name="hero-arrow-down-tray" class="w-4 h-4" /> Import
</button>
<button phx-click="refresh" class="btn btn-ghost btn-sm btn-square">
<.icon name="hero-arrow-path" class="w-4 h-4" />
</button>
</:actions>
</.admin_page_header>
<%!-- Navigation Tabs --%>
<div class="tabs tabs-boxed bg-base-200 p-1 mb-6">
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/settings/billing")}
class="tab"
>
<.icon name="hero-cog-6-tooth" class="w-4 h-4 mr-2" /> General
</.link>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/settings/billing/providers")}
class="tab"
>
<.icon name="hero-credit-card" class="w-4 h-4 mr-2" /> Payment Providers
</.link>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/billing/currencies")}
class="tab tab-active"
>
<.icon name="hero-currency-dollar" class="w-4 h-4 mr-2" /> Currencies
</.link>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/billing/plans")}
class="tab"
>
<.icon name="hero-clipboard-document-list" class="w-4 h-4 mr-2" /> Plans
</.link>
</div>
<%!-- Currencies Table --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-0">
<%= if @loading do %>
<div class="flex justify-center items-center py-12">
<span class="loading loading-spinner loading-lg"></span>
</div>
<% else %>
<%= if Enum.empty?(@currencies) do %>
<div class="text-center py-12">
<.icon
name="hero-currency-dollar"
class="w-16 h-16 mx-auto mb-4 text-base-content/30"
/>
<h3 class="text-xl font-semibold mb-2">No currencies configured</h3>
<p class="text-base-content/60 mb-4">
Add currencies manually or import from the ISO 4217 library
</p>
<div class="flex justify-center gap-2">
<button phx-click="show_add_form" class="btn btn-primary btn-sm">
<.icon name="hero-plus" class="w-4 h-4" /> Add Currency
</button>
<button phx-click="show_import" class="btn btn-ghost btn-sm">
<.icon name="hero-arrow-down-tray" class="w-4 h-4" /> Import
</button>
</div>
</div>
<% else %>
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>Currency</th>
<th>Symbol</th>
<th>Decimal Places</th>
<th>Exchange Rate</th>
<th>Status</th>
<th>Default</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<%= for currency <- @currencies do %>
<tr class="hover">
<td>
<div class="flex items-center gap-3">
<div class="font-mono text-lg font-bold">{currency.code}</div>
<div class="text-sm text-base-content/60">{currency.name}</div>
</div>
</td>
<td>
<span class="text-xl font-bold">{currency.symbol}</span>
</td>
<td>{currency.decimal_places}</td>
<td>
<%= if currency.is_default do %>
<span class="badge badge-ghost">Base</span>
<% else %>
<span class="font-mono">
{Decimal.to_string(currency.exchange_rate)}
</span>
<% end %>
</td>
<td>
<label class="cursor-pointer">
<input
type="checkbox"
class="toggle toggle-success toggle-sm"
checked={currency.enabled}
phx-click="toggle_enabled"
phx-value-uuid={currency.uuid}
/>
</label>
</td>
<td>
<%= if currency.is_default do %>
<span class="badge badge-primary">Default</span>
<% else %>
<button
phx-click="set_default"
phx-value-uuid={currency.uuid}
class="btn btn-ghost btn-xs"
>
Set Default
</button>
<% end %>
</td>
<td>
<div class="flex justify-end gap-1">
<button
phx-click="show_edit_form"
phx-value-uuid={currency.uuid}
class="btn btn-xs btn-ghost"
title="Edit currency"
>
<.icon name="hero-pencil" class="w-4 h-4" />
</button>
<%= unless currency.is_default do %>
<button
phx-click="delete_currency"
phx-value-uuid={currency.uuid}
data-confirm={"Delete #{currency.code}? This cannot be undone."}
class="btn btn-xs btn-ghost text-error"
title="Delete currency"
>
<.icon name="hero-trash" class="w-4 h-4" />
</button>
<% end %>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
<% end %>
</div>
</div>
<%!-- Info Card --%>
<div class="alert alert-info mt-6">
<.icon name="hero-information-circle" class="w-5 h-5" />
<div>
<h3 class="font-bold">Currency Configuration</h3>
<p class="text-sm">
The default currency is used for all new orders. Exchange rates are relative to the default currency (base rate = 1.0).
Enable only the currencies you plan to accept for billing.
</p>
</div>
</div>
</div>
<%!-- Currency Form Modal --%>
<%= if @show_form do %>
<div class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">
{if @editing_currency, do: "Edit Currency", else: "Add Currency"}
</h3>
<.form for={@form} phx-change="validate" phx-submit="save">
<div class="space-y-4">
<%!-- Code --%>
<div class="form-control">
<label class="label"><span class="label-text">Currency Code</span></label>
<input
type="text"
name="currency[code]"
value={@form[:code].value}
maxlength="3"
placeholder="USD"
disabled={@editing_currency != nil}
class={"input input-bordered w-full uppercase #{if @form[:code].errors != [], do: "input-error"}"}
/>
<%= if @form[:code].errors != [] do %>
<label class="label">
<span class="label-text-alt text-error">
{error_to_string(@form[:code].errors)}
</span>
</label>
<% end %>
<%= if @editing_currency do %>
<input type="hidden" name="currency[code]" value={@editing_currency.code} />
<% end %>
</div>
<%!-- Name --%>
<div class="form-control">
<label class="label"><span class="label-text">Name</span></label>
<input
type="text"
name="currency[name]"
value={@form[:name].value}
placeholder="US Dollar"
class={"input input-bordered w-full #{if @form[:name].errors != [], do: "input-error"}"}
/>
<%= 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>
<%!-- Symbol --%>
<div class="form-control">
<label class="label"><span class="label-text">Symbol</span></label>
<input
type="text"
name="currency[symbol]"
value={@form[:symbol].value}
placeholder="$"
maxlength="5"
class={"input input-bordered w-full #{if @form[:symbol].errors != [], do: "input-error"}"}
/>
<%= if @form[:symbol].errors != [] do %>
<label class="label">
<span class="label-text-alt text-error">
{error_to_string(@form[:symbol].errors)}
</span>
</label>
<% end %>
</div>
<div class="grid grid-cols-2 gap-4">
<%!-- Decimal Places --%>
<div class="form-control">
<label class="label"><span class="label-text">Decimal Places</span></label>
<input
type="number"
name="currency[decimal_places]"
value={@form[:decimal_places].value}
min="0"
max="4"
class="input input-bordered w-full"
/>
</div>
<%!-- Exchange Rate --%>
<div class="form-control">
<label class="label"><span class="label-text">Exchange Rate</span></label>
<input
type="text"
name="currency[exchange_rate]"
value={@form[:exchange_rate].value}
placeholder="1.0"
class="input input-bordered w-full"
/>
</div>
</div>
<%!-- Sort Order --%>
<div class="form-control">
<label class="label"><span class="label-text">Sort Order</span></label>
<input
type="number"
name="currency[sort_order]"
value={@form[:sort_order].value}
min="0"
class="input input-bordered w-full"
/>
</div>
<%!-- Enabled --%>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="hidden" name="currency[enabled]" value="false" />
<input
type="checkbox"
name="currency[enabled]"
value="true"
checked={to_string(@form[:enabled].value) == "true"}
class="checkbox checkbox-primary"
/>
<span class="label-text">Enabled</span>
</label>
</div>
</div>
<div class="modal-action">
<button type="button" phx-click="close_form" class="btn">Cancel</button>
<button type="submit" class="btn btn-primary">
{if @editing_currency, do: "Update", else: "Create"}
</button>
</div>
</.form>
</div>
<div class="modal-backdrop" phx-click="close_form"></div>
</div>
<% end %>
<%!-- Import Modal --%>
<%= if @show_import do %>
<div class="modal modal-open">
<div class="modal-box max-w-3xl">
<h3 class="font-bold text-lg mb-4">Import Currencies</h3>
<%= if Enum.empty?(@available_currencies) do %>
<div class="text-center py-8">
<.icon name="hero-check-circle" class="w-12 h-12 mx-auto mb-3 text-success" />
<p class="text-base-content/60">All available currencies have already been added</p>
</div>
<% else %>
<div class="flex items-center justify-between mb-4">
<span class="text-sm text-base-content/60">
{MapSet.size(@selected_imports)} of {length(@available_currencies)} selected
</span>
<div class="flex gap-2">
<button phx-click="select_all_imports" class="btn btn-ghost btn-xs">
Select All
</button>
<button phx-click="deselect_all_imports" class="btn btn-ghost btn-xs">
Deselect All
</button>
</div>
</div>
<div class="overflow-y-auto max-h-96 border border-base-300 rounded-lg">
<table class="table table-sm">
<thead class="sticky top-0 bg-base-200">
<tr>
<th class="w-10"></th>
<th>Code</th>
<th>Name</th>
<th>Symbol</th>
<th>Decimals</th>
</tr>
</thead>
<tbody>
<%= for cur <- @available_currencies do %>
<tr
phx-click="toggle_import_selection"
phx-value-code={cur.code}
class={"hover cursor-pointer #{if MapSet.member?(@selected_imports, cur.code), do: "bg-primary/10"}"}
>
<td>
<input
type="checkbox"
class="checkbox checkbox-primary checkbox-sm"
checked={MapSet.member?(@selected_imports, cur.code)}
tabindex="-1"
/>
</td>
<td class="font-mono font-bold">{cur.code}</td>
<td>{cur.name}</td>
<td class="text-lg">{cur.symbol_native}</td>
<td>{cur.decimal_digits}</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
<div class="modal-action">
<button type="button" phx-click="close_import" class="btn">Cancel</button>
<%= unless Enum.empty?(@available_currencies) do %>
<button
phx-click="import_selected"
disabled={MapSet.size(@selected_imports) == 0}
class="btn btn-primary"
>
Import {MapSet.size(@selected_imports)} Currencies
</button>
<% end %>
</div>
</div>
<div class="modal-backdrop" phx-click="close_import"></div>
</div>
<% end %>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>