towerops/lib/towerops_web/live/admin/org_live/index.html.heex
Graham McIntire 4e3f732f21
ui: comprehensive button styling audit - convert all text-styled actions to proper buttons
Fixed 10 text-styled action buttons across 7 files:

Agent Management:
- Delete Agent buttons (2x) → danger variant
- View Setup links (already fixed) → secondary variant

Organization Settings:
- Remove Member button → danger variant

Admin Pages:
- Edit Overrides button → secondary variant
- Delete Organization button → danger variant
- Remove IP/CIDR button → danger variant
- Delete User button → danger variant

Device Management:
- Delete Backup button → danger variant

All action buttons now use proper .button component with appropriate
variants (danger for destructive actions, secondary for neutral actions).
This provides consistent visual hierarchy and makes destructive actions
more clearly identifiable.
2026-03-09 15:32:42 -05:00

305 lines
12 KiB
Text

<Layouts.admin flash={@flash} timezone={@timezone}>
<div class="space-y-6">
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{t("All Organizations")}</h1>
<p class="text-gray-600 dark:text-gray-400">{t_admin("Manage organizations")}</p>
</div>
<!-- Global Billing Defaults Card -->
<div
class="rounded-lg border border-slate-200 bg-white p-6 dark:border-slate-700 dark:bg-slate-800"
data-test="global-defaults-card"
>
<div class="flex items-center justify-between">
<div>
<h3 class="text-lg font-medium text-slate-900 dark:text-white">
Global Billing Defaults
</h3>
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">
Default pricing for all organizations (can be overridden per-org)
</p>
</div>
<button
type="button"
phx-click="edit_global_defaults"
data-test="edit-global-defaults"
class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500"
>
Edit Defaults
</button>
</div>
<dl class="mt-6 grid grid-cols-2 gap-6">
<div>
<dt class="text-sm font-medium text-slate-500 dark:text-slate-400">Free Devices</dt>
<dd
class="mt-1 text-2xl font-semibold text-slate-900 dark:text-white"
data-test="default-free-devices"
>
{@default_free_devices}
</dd>
</div>
<div>
<dt class="text-sm font-medium text-slate-500 dark:text-slate-400">
Price Per Device
</dt>
<dd
class="mt-1 text-2xl font-semibold text-slate-900 dark:text-white"
data-test="default-price"
>
${@default_price}/mo
</dd>
</div>
</dl>
</div>
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
<.table id="organizations" rows={@organizations}>
<:col :let={org} label={t("Name")}>{org.name}</:col>
<:col :let={org} label={t("Slug")}>{org.slug}</:col>
<:col :let={org} label={t("Plan")}>
<span class={[
"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
if(org.subscription_plan == "paid",
do: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400",
else: "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"
)
]}>
{org.subscription_plan}
</span>
</:col>
<:col :let={org} label={t("Devices")}>
{org.device_count}
</:col>
<:col :let={org} label={t("Overrides")}>
<%= if org.custom_free_device_limit || org.custom_price_per_device do %>
<div class="flex flex-wrap gap-1">
<%= if org.custom_free_device_limit do %>
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400">
{org.custom_free_device_limit} {t("free")}
</span>
<% end %>
<%= if org.custom_price_per_device do %>
<span class="inline-flex items-center rounded-full bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-700 dark:bg-purple-900/30 dark:text-purple-400">
${org.custom_price_per_device}/{t("device")}
</span>
<% end %>
</div>
<% else %>
<span class="text-gray-400 dark:text-gray-500 text-xs">{t("Default")}</span>
<% end %>
</:col>
<:col :let={org} label={t("Members")}>{length(org.memberships)}</:col>
<:col :let={org} label={t("Created")}>
{ToweropsWeb.TimeHelpers.format_date(org.inserted_at, @timezone)}
</:col>
<:col :let={org} label="">
<div class="flex items-center gap-3">
<.button
id={"edit-overrides-#{org.id}"}
phx-click="edit_overrides"
phx-value-id={org.id}
variant="secondary"
>
{t_admin("Edit")}
</.button>
<.button
phx-click="delete_org"
phx-value-id={org.id}
data-confirm={
t(
"Are you sure? This will delete all sites, device, and data for this organization."
)
}
variant="danger"
>
{t("Delete")}
</.button>
</div>
</:col>
</.table>
</div>
<%!-- Billing Override Edit Panel --%>
<%= if @editing_org do %>
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
{t_admin("Billing Overrides")} — {@editing_org.name}
</h2>
<button
id="cancel-overrides-btn"
phx-click="cancel_overrides"
class="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
>
{t("Cancel")}
</button>
</div>
<.form
for={@override_form}
id="billing-override-form"
phx-change="validate_overrides"
phx-submit="save_overrides"
>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<.input
field={@override_form[:custom_free_device_limit]}
type="number"
label={t_admin("Custom Free Device Limit")}
placeholder="10 (default)"
min="1"
max="9999"
/>
<.input
field={@override_form[:custom_price_per_device]}
type="number"
label={t_admin("Custom Price Per Device")}
placeholder="1.00 (default)"
step="0.01"
min="0"
max="999.99"
/>
</div>
<div class="mt-4 flex items-center gap-3">
<.button type="submit" phx-disable-with={t("Saving...")}>
{t("Save")}
</.button>
<button
id="clear-overrides-btn"
type="button"
phx-click="clear_overrides"
class="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
>
{t_admin("Reset to Defaults")}
</button>
</div>
</.form>
</div>
<% end %>
<%!-- Edit Global Defaults Modal --%>
<%= if @editing_global do %>
<div
class="fixed inset-0 z-50 overflow-y-auto"
data-test="global-defaults-modal"
>
<div class="flex min-h-screen items-center justify-center p-4">
<div
class="fixed inset-0 bg-slate-500 bg-opacity-75"
phx-click="cancel_global_edit"
>
</div>
<div class="relative w-full max-w-lg rounded-lg bg-white p-6 shadow-xl dark:bg-slate-800">
<h3 class="text-lg font-medium text-slate-900 dark:text-white">
Edit Global Billing Defaults
</h3>
<.form
for={@global_form}
phx-change="validate_global_pricing"
phx-submit="save_global_pricing"
data-test="global-defaults-form"
class="mt-6 space-y-4"
>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300">
Free Devices
</label>
<input
type="number"
name="global_pricing[default_free_devices]"
value={@global_form.data["default_free_devices"]}
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-slate-600 dark:bg-slate-700"
/>
<%= if error = @global_form.errors[:default_free_devices] do %>
<p
id="global_pricing_default_free_devices-error"
class="mt-1 text-sm text-red-600"
>
{elem(error, 0)}
</p>
<% end %>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300">
Price Per Device (USD/month)
</label>
<input
type="number"
step="0.01"
name="global_pricing[default_price_per_device]"
value={@global_form.data["default_price_per_device"]}
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-slate-600 dark:bg-slate-700"
/>
<%= if error = @global_form.errors[:default_price_per_device] do %>
<p
id="global_pricing_default_price_per_device-error"
class="mt-1 text-sm text-red-600"
>
{elem(error, 0)}
</p>
<% end %>
</div>
<div class="flex justify-end gap-3 pt-4">
<button
type="button"
phx-click="cancel_global_edit"
data-test="cancel-global-edit"
class="rounded-md px-3 py-2 text-sm font-semibold text-slate-900 hover:bg-slate-100 dark:text-white dark:hover:bg-slate-700"
>
Cancel
</button>
<button
type="submit"
class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500"
>
Save Changes
</button>
</div>
</.form>
<%!-- Confirmation Dialog --%>
<%= if @show_confirm do %>
<div
class="absolute inset-0 flex items-center justify-center bg-slate-900/50"
data-test="confirm-pricing-change"
>
<div class="w-full max-w-sm rounded-lg bg-white p-6 shadow-xl dark:bg-slate-800">
<h4 class="text-lg font-medium text-slate-900 dark:text-white">
Confirm Price Change
</h4>
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
This will update {@active_sub_count} active subscriptions to the new price.
Changes take effect at the next billing cycle.
</p>
<div class="mt-6 flex justify-end gap-3">
<button
type="button"
phx-click="cancel_global_edit"
class="rounded-md px-3 py-2 text-sm font-semibold text-slate-900 hover:bg-slate-100 dark:text-white dark:hover:bg-slate-700"
>
Cancel
</button>
<button
type="button"
phx-click="confirm_pricing_update"
data-test="confirm-pricing-update"
class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500"
>
Confirm Update
</button>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</Layouts.admin>