feat(operator): create tenant from the operator UI

Wires the previously-dead 'New tenant' button on /tenants to a modal
that collects slug + name + plan + optional primary domain, POSTs to
the existing platform-api /tenants endpoint via a new operator proxy,
and navigates into the freshly-created tenant detail page. Slug
auto-derives from the name until the operator types in the slug field
themselves. Billing details and provisioning are still done from the
tenant detail page after creation — this modal is the minimum that
backend validators will accept.
This commit is contained in:
Ronni Baslund
2026-05-24 22:31:49 +02:00
parent 114b419a69
commit be430179d9
3 changed files with 284 additions and 1 deletions
+13 -1
View File
@@ -7,6 +7,16 @@ const { data: tenants, refresh, pending } = await useFetch<Tenant[]>('/api/tenan
const search = ref('')
const statusFilter = ref<'all' | TenantStatus>('all')
const createOpen = ref(false)
async function onCreated(tenant: Tenant) {
createOpen.value = false
// Refresh the list so the new row appears immediately, then jump into the
// detail page — that's where the operator will configure domains, billing,
// and trigger provisioning.
await refresh()
await navigateTo(`/tenants/${tenant.slug}`)
}
const filtered = computed(() => {
const q = search.value.trim().toLowerCase()
@@ -50,13 +60,15 @@ function navTo(t: Tenant) {
<template #leading><UiIcon name="refresh" :size="13" /></template>
Refresh
</UiButton>
<UiButton variant="primary">
<UiButton variant="primary" @click="createOpen = true">
<template #leading><UiIcon name="plus" :size="13" /></template>
New tenant
</UiButton>
</template>
</PageHeader>
<NewTenantModal :open="createOpen" @close="createOpen = false" @created="onCreated" />
<div class="stage">
<div class="filters">
<div class="search">