@saasicat/ui-vue 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/{catalog-BY_t9prv.d.cts → catalog-DJcfm5N2.d.cts} +20 -7
  2. package/dist/{catalog-BY_t9prv.d.ts → catalog-DJcfm5N2.d.ts} +20 -7
  3. package/dist/{chunk-JN6HZGLV.js → chunk-F7NRM6KI.js} +54 -28
  4. package/dist/{chunk-7IFKVGZV.js → chunk-LY52T4N4.js} +21 -2
  5. package/dist/{chunk-OTP3OSSW.js → chunk-XMMFN2WJ.js} +74 -3
  6. package/dist/client/index.cjs +132 -29
  7. package/dist/client/index.d.cts +53 -3
  8. package/dist/client/index.d.ts +53 -3
  9. package/dist/client/index.js +14 -2
  10. package/dist/index.cjs +319 -162
  11. package/dist/index.d.cts +30 -8
  12. package/dist/index.d.ts +30 -8
  13. package/dist/index.js +176 -133
  14. package/dist/quasar/index.cjs +76 -29
  15. package/dist/quasar/index.d.cts +2 -2
  16. package/dist/quasar/index.d.ts +2 -2
  17. package/dist/quasar/index.js +6 -2
  18. package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
  19. package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
  20. package/dist/{use-super-admin-i18n-c_Eqsu9u.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
  21. package/dist/{use-super-admin-i18n-DlJRGIVq.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
  22. package/package.json +18 -8
  23. package/src/client/i18n/messages/bundles.ts +0 -2
  24. package/src/client/i18n/messages/common.ts +22 -0
  25. package/src/client/i18n/messages/discovery.ts +2 -2
  26. package/src/client/i18n/messages/marketing.ts +2 -4
  27. package/src/client/i18n/messages/plan-detail.ts +0 -4
  28. package/src/client/i18n/messages/plans.ts +20 -8
  29. package/src/client/i18n/messages/tenants.ts +0 -2
  30. package/src/client/index.ts +1 -0
  31. package/src/client/manifest-loader.ts +1 -1
  32. package/src/client/resolve-plans.ts +144 -0
  33. package/src/components/BundleVersionPublishDialog.vue +6 -6
  34. package/src/components/KvBlock.vue +4 -4
  35. package/src/components/MarketingPromotionsTab.vue +34 -48
  36. package/src/components/MfaPromptDialog.vue +8 -2
  37. package/src/components/TenantActionConfirmDialog.vue +4 -4
  38. package/src/components/VersionDiffPreview.vue +7 -7
  39. package/src/components/admin-page/AdminFilters.vue +12 -0
  40. package/src/components/admin-page/AdminHero.vue +22 -5
  41. package/src/components/admin-page/AdminKpi.vue +84 -0
  42. package/src/components/admin-page/AdminPaginator.vue +195 -0
  43. package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
  44. package/src/components/admin-page/AdminSection.vue +31 -16
  45. package/src/components/admin-page/AdminStatistics.vue +40 -0
  46. package/src/components/admin-page/AdminTable.vue +175 -0
  47. package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
  48. package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
  49. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
  50. package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
  51. package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
  52. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
  53. package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
  54. package/src/components/dialogs/PilotCreateDialog.vue +12 -12
  55. package/src/components/dialogs/PilotEditDialog.vue +7 -7
  56. package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
  57. package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
  58. package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
  59. package/src/components/plan/PlanGrid.vue +1 -1
  60. package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
  61. package/src/components/plan-detail/PlanDetail.vue +92 -302
  62. package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
  63. package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
  64. package/src/components/plan-list/PlanList.vue +72 -290
  65. package/src/components/plan-matrix/PlanMatrix.vue +66 -183
  66. package/src/components/plan-review/PlanReview.vue +191 -275
  67. package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
  68. package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
  69. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
  70. package/src/index.ts +1 -0
  71. package/src/pages-standard/AdminLayout.vue +34 -10
  72. package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
  73. package/src/pages-standard/AuditPage.vue +53 -72
  74. package/src/pages-standard/BundlesPage.vue +128 -144
  75. package/src/pages-standard/DashboardPage.vue +43 -107
  76. package/src/pages-standard/DiscoveryPage.vue +158 -200
  77. package/src/pages-standard/EmailHistoryPage.vue +98 -88
  78. package/src/pages-standard/MarketingCatalogPage.vue +237 -267
  79. package/src/pages-standard/PilotsPage.vue +44 -60
  80. package/src/pages-standard/PlansPage.vue +230 -105
  81. package/src/pages-standard/PlatformEmailPage.vue +37 -40
  82. package/src/pages-standard/PromoCodeDetailPage.vue +16 -30
  83. package/src/pages-standard/PromoCodesPage.vue +86 -112
  84. package/src/pages-standard/SubscriptionsPage.vue +10 -17
  85. package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
  86. package/src/pages-standard/SuperAdminSetupWizard.vue +20 -17
  87. package/src/pages-standard/TenantDetailPage.vue +45 -41
  88. package/src/pages-standard/TenantsPage.vue +184 -264
  89. package/src/pages-standard/UsersPage.vue +69 -104
  90. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
  91. package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
  92. package/src/pages-standard/bundles-page/BundlesToolbar.vue +6 -10
  93. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
  94. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
  95. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
  96. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
  97. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
  98. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +1 -1
  99. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +2 -13
  100. package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
  101. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
  102. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
  103. package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
  104. package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
  105. package/src/pages-standard/plans-page/PlansPageToast.vue +1 -1
  106. package/src/pages-standard/sa-theme.css +481 -71
  107. package/src/pages-standard/tenant-detail/TenantUsers.vue +2 -8
  108. package/src/pages-standard/tenants/StatusPill.vue +2 -2
  109. package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
  110. package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
  111. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  112. package/src/pages-tenant/TenantPlanSection.vue +12 -20
  113. package/src/quasar/create-super-admin-app.ts +4 -0
  114. package/src/vue/entitlement-provider.ts +0 -1
  115. package/src/vue/feature-router-guard.ts +0 -1
  116. package/src/vue/super-admin-context.ts +106 -1
  117. package/src/vue/use-api-list.ts +2 -1
  118. package/src/vue/use-pagination.ts +63 -0
  119. package/src/vue/use-plans.ts +1 -2
  120. package/src/vue/use-sign-out.ts +90 -0
  121. package/src/vue/use-tenant-billing.ts +1 -1
  122. package/src/vue/use-tenant-manifest.ts +0 -1
  123. package/src/components/KpiCard.vue +0 -53
  124. package/src/components/plan-cockpit/PlanCockpit.vue +0 -1027
  125. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
  126. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -99
  127. package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
  128. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
  129. package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
  130. package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -249
  131. package/src/components/plan-cockpit/types.ts +0 -72
  132. package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
@@ -2,139 +2,135 @@
2
2
  <AdminPage class="sa-tenants">
3
3
  <AdminHero :title="msg.list.title" :subtitle="subtitle" />
4
4
 
5
- <div class="sa-tenants__filter">
6
- <div class="sa-tenants__search">
7
- <q-icon name="search" size="15px" />
8
- <input
9
- v-model="searchInput"
10
- :placeholder="msg.list.searchPlaceholder"
11
- @input="reloadDebounced"
12
- />
13
- <button v-if="searchInput" class="sa-tenants__clear" @click="onClearSearch">
14
- <q-icon name="close" size="13px" />
15
- </button>
16
- </div>
17
- <select v-model="statusFilter" class="sa-tenants__select" @change="applyFilter">
18
- <option :value="null">{{ msg.list.allStatuses }}</option>
19
- <option v-for="opt in resolvedStatusOptions" :key="opt.value" :value="opt.value">
20
- {{ opt.label }}
21
- </option>
22
- </select>
23
- <select
24
- v-if="planOptions && planOptions.length > 0"
25
- v-model="planFilter"
26
- class="sa-tenants__select"
27
- @change="applyFilter"
28
- >
29
- <option :value="null">{{ resolvedPlanFilterLabel }}</option>
30
- <option v-for="p in planOptions" :key="p" :value="p">{{ p }}</option>
31
- </select>
32
- <slot name="filters-extra" />
33
- </div>
34
-
35
- <q-banner v-if="error" class="bg-red-1 text-red-9 q-mb-md" rounded>
36
- <strong>{{ common.error }}:</strong> {{ error.message }}
37
- </q-banner>
38
-
39
- <div class="sa-tenants__card">
40
- <div class="sa-tenants__wrap">
41
- <table>
42
- <thead>
43
- <tr>
44
- <th>{{ msg.tenant }}</th>
45
- <th v-if="showPlanColumn">{{ resolvedPlanColumnLabel }}</th>
46
- <th>{{ common.status }}</th>
47
- <th v-if="usageFields.length > 0" class="num">
48
- {{ msg.list.columnUsage }}
49
- </th>
50
- <th class="num">{{ msg.list.columnCreatedAt }}</th>
51
- <th v-if="hasActions" class="num"></th>
52
- </tr>
53
- </thead>
54
- <tbody>
55
- <tr v-for="row in items" :key="row.id">
56
- <td>
57
- <div class="sa-tenants__tenant">
58
- <div class="sa-tenants__avatar" :style="avatarStyle(row)">
59
- {{ tenantInitials(row.name) }}
60
- </div>
61
- <div>
62
- <div class="sa-tenants__name">{{ row.name }}</div>
63
- <div class="sa-tenants__slug">{{ row.slug }}</div>
64
- </div>
5
+ <AdminBody>
6
+ <q-banner v-if="error" class="bg-red-1 text-red-9 q-mb-md" rounded>
7
+ <strong>{{ common.error }}:</strong> {{ error.message }}
8
+ </q-banner>
9
+
10
+ <AdminSection class="sa-tenants__card">
11
+ <AdminFilters class="q-mb-lg">
12
+ <q-input
13
+ v-model="searchInput"
14
+ outlined
15
+ dense
16
+ clearable
17
+ :label="msg.list.searchPlaceholder"
18
+ debounce="250"
19
+ @update:model-value="applyFilter"
20
+ >
21
+ <template #prepend><q-icon name="search" size="18px" /></template>
22
+ </q-input>
23
+ <q-select
24
+ v-model="statusFilter"
25
+ outlined
26
+ dense
27
+ clearable
28
+ emit-value
29
+ map-options
30
+ :options="statusSelectOptions"
31
+ :label="msg.list.allStatuses"
32
+ @update:model-value="applyFilter"
33
+ />
34
+ <q-select
35
+ v-if="planOptions && planOptions.length > 0"
36
+ v-model="planFilter"
37
+ outlined
38
+ dense
39
+ clearable
40
+ :options="planOptions"
41
+ :label="resolvedPlanFilterLabel"
42
+ @update:model-value="applyFilter"
43
+ />
44
+ <slot name="filters-extra" />
45
+ </AdminFilters>
46
+
47
+ <AdminTable
48
+ server-side
49
+ :rows="items"
50
+ :columns="tenantColumns"
51
+ :loading="loading"
52
+ :page="page"
53
+ :rows-per-page="rowsPerPage"
54
+ :total="total"
55
+ :empty-text="msg.list.empty"
56
+ storage-key="tenants"
57
+ @update:page="goToPage"
58
+ @update:rows-per-page="setPageSize"
59
+ >
60
+ <template #body-cell-tenant="{ row }">
61
+ <q-td>
62
+ <div class="sa-tenants__tenant">
63
+ <div class="sa-tenants__avatar" :style="avatarStyle(row)">
64
+ {{ tenantInitials(row.name) }}
65
65
  </div>
66
- </td>
67
- <td v-if="showPlanColumn">
68
- <span class="sa-tenants__plan">
69
- <span
70
- class="sa-tenants__plan-dot"
71
- :style="{ background: planAccentFor(row) }"
72
- />
73
- {{ planLabel(row) }}
74
- </span>
75
- </td>
76
- <td>
77
- <div class="sa-tenants__pills">
78
- <slot name="status-pills" :row="row">
79
- <StatusPill
80
- v-for="(p, i) in resolvedPills(row)"
81
- :key="i"
82
- :label="p.label"
83
- :icon="p.icon"
84
- :tone="p.tone"
85
- />
86
- </slot>
87
- </div>
88
- </td>
89
- <td v-if="usageFields.length > 0" class="num">
90
- <div
91
- v-for="(uf, i) in usageFields"
92
- :key="i"
93
- class="sa-tenants__usage"
94
- >
95
- <q-icon :name="uf.icon" size="11px" />
96
- {{ usageValue(row, uf) }}
66
+ <div>
67
+ <div class="sa-tenants__name">{{ row.name }}</div>
68
+ <div class="sa-tenants__slug">{{ row.slug }}</div>
97
69
  </div>
98
- </td>
99
- <td class="num sa-tenants__mono">
100
- {{ formatCreatedAt(row) }}
101
- </td>
102
- <td v-if="hasActions" class="num sa-tenants__actions">
103
- <slot name="row-actions" :row="row" :actions="visibleActions(row)">
104
- <component
105
- :is="action.to ? 'a' : 'button'"
106
- v-for="action in visibleActions(row)"
107
- :key="action.id"
108
- :href="action.to ? action.to(row) : undefined"
109
- class="sa-tenants__icon-btn"
110
- :class="
111
- action.tone
112
- ? `sa-tenants__icon-btn--${action.tone}`
113
- : ''
114
- "
115
- :title="action.label"
116
- @click="action.handler ? action.handler(row) : undefined"
117
- >
118
- <q-icon :name="action.icon" size="15px" />
119
- </component>
70
+ </div>
71
+ </q-td>
72
+ </template>
73
+
74
+ <template #body-cell-plan="{ row }">
75
+ <q-td>
76
+ <span class="sa-tenants__plan">
77
+ <span
78
+ class="sa-tenants__plan-dot"
79
+ :style="{ background: planAccentFor(row) }"
80
+ />
81
+ {{ planLabel(row) }}
82
+ </span>
83
+ </q-td>
84
+ </template>
85
+
86
+ <template #body-cell-status="{ row }">
87
+ <q-td>
88
+ <div class="sa-tenants__pills">
89
+ <slot name="status-pills" :row="row">
90
+ <StatusPill
91
+ v-for="(p, i) in resolvedPills(row)"
92
+ :key="i"
93
+ :label="p.label"
94
+ :icon="p.icon"
95
+ :tone="p.tone"
96
+ />
120
97
  </slot>
121
- </td>
122
- </tr>
123
- </tbody>
124
- </table>
125
- <div v-if="items.length === 0 && !loading" class="sa-tenants__empty">
126
- <q-icon name="search_off" size="32px" />
127
- <div>{{ msg.list.empty }}</div>
128
- </div>
129
- <div v-if="loading" class="sa-tenants__loading">
130
- <q-spinner size="20px" /> {{ common.loading }}
131
- </div>
132
- </div>
133
- </div>
134
-
135
- <footer class="sa-tenants__foot">
136
- {{ paginationLabel }}
137
- </footer>
98
+ </div>
99
+ </q-td>
100
+ </template>
101
+
102
+ <template #body-cell-usage="{ row }">
103
+ <q-td class="text-right">
104
+ <div v-for="(uf, i) in usageFields" :key="i" class="sa-tenants__usage">
105
+ <q-icon :name="uf.icon" size="11px" />
106
+ {{ usageValue(row, uf) }}
107
+ </div>
108
+ </q-td>
109
+ </template>
110
+
111
+ <template #body-cell-createdAt="{ row }">
112
+ <q-td class="text-right sa-tenants__mono">{{ formatCreatedAt(row) }}</q-td>
113
+ </template>
114
+
115
+ <template v-if="hasActions" #row-actions="{ row }">
116
+ <slot name="row-actions" :row="row" :actions="visibleActions(row)">
117
+ <component
118
+ :is="action.to ? 'a' : 'button'"
119
+ v-for="action in visibleActions(row)"
120
+ :key="action.id"
121
+ :href="action.to ? action.to(row) : undefined"
122
+ class="sa-tenants__icon-btn"
123
+ :class="action.tone ? `sa-tenants__icon-btn--${action.tone}` : ''"
124
+ :title="action.label"
125
+ @click="action.handler ? action.handler(row) : undefined"
126
+ >
127
+ <q-icon :name="action.icon" size="15px" />
128
+ </component>
129
+ </slot>
130
+ </template>
131
+ </AdminTable>
132
+ </AdminSection>
133
+ </AdminBody>
138
134
 
139
135
  <!-- Manifest-Driven Action Flow: only mounted when the `manifest` prop
140
136
  is set AND `manifestActionsEnabled` is true (default when manifest
@@ -161,12 +157,16 @@
161
157
  </template>
162
158
 
163
159
  <script setup lang="ts">
160
+ import AdminTable from '../components/admin-page/AdminTable.vue';
164
161
  import { computed, ref, watch } from 'vue';
162
+ import type { QTableColumn } from 'quasar';
163
+ import AdminBody from '../components/admin-page/AdminBody.vue';
164
+ import AdminFilters from '../components/admin-page/AdminFilters.vue';
165
165
  import AdminHero from '../components/admin-page/AdminHero.vue';
166
+ import AdminSection from '../components/admin-page/AdminSection.vue';
166
167
  import AdminPage from '../components/admin-page/AdminPage.vue';
167
168
  import type { AdminManifest, TenantActionDef, TenantDto, TenantListFilter } from '@saasicat/types';
168
169
  import type { HttpClient } from '../client/types.js';
169
- import { formatMessage } from '../client/i18n/format.js';
170
170
  import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
171
171
  import { useTenants } from '../vue/use-tenants.js';
172
172
  import {
@@ -333,6 +333,9 @@ const msg = useSaMessages('tenants');
333
333
  const common = useSaMessages('common');
334
334
  const { intlLocale } = useSuperAdminI18n();
335
335
 
336
+ const statusSelectOptions = computed(() =>
337
+ resolvedStatusOptions.value.map((o) => ({ label: o.label, value: o.value })),
338
+ );
336
339
  const resolvedStatusOptions = computed<ReadonlyArray<{ value: string; label: string }>>(
337
340
  () =>
338
341
  props.statusOptions ?? [
@@ -354,28 +357,42 @@ const list = useTenants<TenantRow>({
354
357
  http: props.http,
355
358
  getAuthToken: props.getAuthToken,
356
359
  });
357
- const { items, page, total, loading, error, goToPage, setPageSize } = list;
360
+ // Aliased: the `pageSize` prop is the INITIAL size, `list.pageSize` is the
361
+ // current one. Sharing one name makes the template ambiguous about which of
362
+ // the two it reads.
363
+ const { items, page, pageSize: rowsPerPage, total, loading, error, goToPage, setPageSize } = list;
358
364
 
359
365
  setPageSize(props.pageSize);
360
366
 
361
- const paginationLabel = computed(() =>
362
- formatMessage(msg.value.list.pagination, {
363
- total: total.value,
364
- page: page.value,
365
- pages: Math.max(1, Math.ceil(total.value / props.pageSize)),
366
- }),
367
- );
368
-
369
- let searchTimer: ReturnType<typeof setTimeout> | null = null;
370
- function reloadDebounced(): void {
371
- if (searchTimer) clearTimeout(searchTimer);
372
- searchTimer = setTimeout(applyFilter, 250);
373
- }
374
-
375
- function onClearSearch(): void {
376
- searchInput.value = '';
377
- applyFilter();
378
- }
367
+ const tenantColumns = computed<QTableColumn[]>(() => {
368
+ const cols: QTableColumn[] = [
369
+ { name: 'tenant', label: msg.value.tenant, field: 'name', align: 'left' },
370
+ ];
371
+ if (props.showPlanColumn) {
372
+ cols.push({
373
+ name: 'plan',
374
+ label: resolvedPlanColumnLabel.value,
375
+ field: 'plan',
376
+ align: 'left',
377
+ });
378
+ }
379
+ cols.push({ name: 'status', label: common.value.status, field: 'status', align: 'left' });
380
+ if (props.usageFields.length > 0) {
381
+ cols.push({
382
+ name: 'usage',
383
+ label: msg.value.list.columnUsage,
384
+ field: 'id',
385
+ align: 'right',
386
+ });
387
+ }
388
+ cols.push({
389
+ name: 'createdAt',
390
+ label: msg.value.list.columnCreatedAt,
391
+ field: 'createdAt',
392
+ align: 'right',
393
+ });
394
+ return cols;
395
+ });
379
396
 
380
397
  function applyFilter(): void {
381
398
  // The status value is passed through unchanged. Apps configure the values
@@ -431,7 +448,6 @@ if (manifestFlow && typeof window !== 'undefined') {
431
448
  manifestFlow.realOrphans,
432
449
  (orphans) => {
433
450
  if (orphans.length > 0) {
434
- // eslint-disable-next-line no-console
435
451
  console.warn(
436
452
  '[PlatformTenantsPage] Manifest actions without a handler in createSuperAdminApp({ actions }):',
437
453
  orphans,
@@ -453,7 +469,6 @@ const combinedActions = computed<TenantRowAction[]>(() => {
453
469
  const filteredCustom = custom.filter((a) => {
454
470
  if (a.actionKey && manifestKeys.has(a.actionKey)) {
455
471
  if (typeof window !== 'undefined') {
456
- // eslint-disable-next-line no-console
457
472
  console.warn(
458
473
  `[PlatformTenantsPage] Custom action "${a.id}" (actionKey="${a.actionKey}") ` +
459
474
  `collides with a manifest action — the manifest wins.`,
@@ -515,89 +530,17 @@ function visibleActions(row: TenantRow): TenantRowAction[] {
515
530
  </script>
516
531
 
517
532
  <style scoped>
518
- .sa-tenants__filter {
519
- padding: 0 0 12px;
520
- display: flex;
521
- gap: 10px;
522
- flex-wrap: wrap;
523
- }
524
- .sa-tenants__search {
525
- flex: 1;
526
- min-width: 240px;
527
- display: flex;
528
- align-items: center;
529
- gap: 6px;
530
- background: #fff;
531
- border: 1px solid var(--sa-border, #e2e8f0);
532
- border-radius: 8px;
533
- padding: 6px 10px;
534
- }
535
- .sa-tenants__search :deep(.q-icon) {
536
- color: var(--sa-muted, #64748b);
537
- }
538
- .sa-tenants__search input {
539
- flex: 1;
540
- border: none;
541
- outline: none;
542
- background: transparent;
543
- font-size: 13px;
544
- font-family: inherit;
545
- color: var(--sa-body, #1e293b);
546
- min-width: 0;
547
- }
548
- .sa-tenants__clear {
549
- background: transparent;
550
- border: none;
551
- cursor: pointer;
552
- padding: 2px;
553
- }
554
- .sa-tenants__select {
555
- padding: 7px 10px;
556
- font-size: 12.5px;
557
- font-family: inherit;
558
- border: 1px solid var(--sa-border, #e2e8f0);
559
- border-radius: 8px;
560
- background: #fff;
561
- cursor: pointer;
562
- }
563
-
564
- .sa-tenants__card {
565
- margin: 0 0 16px;
566
- background: #fff;
567
- border: 1px solid var(--sa-border, #e2e8f0);
568
- border-radius: 12px;
569
- overflow: hidden;
570
- box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
571
- }
572
- .sa-tenants__wrap {
573
- overflow-x: auto;
574
- }
575
- table {
576
- width: 100%;
577
- border-collapse: collapse;
578
- font-size: 13px;
579
- min-width: 800px;
580
- }
581
- thead tr {
582
- background: #fafbfc;
583
- }
584
- th {
585
- padding: 10px 14px;
586
- font-size: 10.5px;
587
- font-weight: 700;
588
- letter-spacing: 0.08em;
589
- color: var(--sa-muted, #64748b);
590
- text-transform: uppercase;
591
- text-align: left;
592
- }
593
- td {
594
- padding: 11px 14px;
595
- vertical-align: middle;
596
- border-top: 1px solid var(--sa-border-soft, #f1f5f9);
597
- }
598
- .num {
599
- text-align: right;
600
- }
533
+ /* No table rules here on purpose — and not because they would be inert.
534
+ Content passed into a child component's slot keeps THIS component's scope
535
+ id, so the `table`, `thead`, `th`, `td` and `.num` rules that used to sit
536
+ here did reach `AdminTable`'s cells. That was the bug: they drew a second
537
+ border on top of the one AdminTable already draws, and the tenant list was
538
+ the only list in the package with doubled row separators. Removing them
539
+ took `border-top-width` from 1px back to 0px.
540
+
541
+ The rule to carry forward: styling a table here means overriding AdminTable
542
+ for one page. If a table needs to look different, that belongs in
543
+ AdminTable, behind a prop. */
601
544
  .sa-tenants__mono {
602
545
  font-family: var(--sa-font-mono, ui-monospace, monospace);
603
546
  font-size: 12px;
@@ -623,11 +566,11 @@ td {
623
566
  .sa-tenants__name {
624
567
  font-size: 13px;
625
568
  font-weight: 600;
626
- color: var(--sa-heading, #0f172a);
569
+ color: var(--sa-heading, var(--sa-heading));
627
570
  }
628
571
  .sa-tenants__slug {
629
572
  font-size: 11px;
630
- color: var(--sa-muted, #64748b);
573
+ color: var(--sa-muted, var(--sa-muted));
631
574
  font-family: var(--sa-font-mono, ui-monospace, monospace);
632
575
  }
633
576
 
@@ -653,16 +596,13 @@ td {
653
596
  align-items: center;
654
597
  gap: 4px;
655
598
  font-size: 12px;
656
- color: var(--sa-muted-dark, #475569);
599
+ color: var(--sa-muted-dark, var(--sa-muted-dark));
657
600
  margin-left: 8px;
658
601
  }
659
602
  .sa-tenants__usage :deep(.q-icon) {
660
- color: var(--sa-muted, #64748b);
603
+ color: var(--sa-muted, var(--sa-muted));
661
604
  }
662
605
 
663
- .sa-tenants__actions {
664
- white-space: nowrap;
665
- }
666
606
  .sa-tenants__icon-btn {
667
607
  background: transparent;
668
608
  border: none;
@@ -674,10 +614,10 @@ td {
674
614
  align-items: center;
675
615
  justify-content: center;
676
616
  text-decoration: none;
677
- color: var(--sa-muted, #64748b);
617
+ color: var(--sa-muted, var(--sa-muted));
678
618
  }
679
619
  .sa-tenants__icon-btn:hover {
680
- background: #f1f5f9;
620
+ background: var(--sa-border-soft);
681
621
  }
682
622
  .sa-tenants__icon-btn :deep(.q-icon) {
683
623
  color: inherit;
@@ -697,24 +637,4 @@ td {
697
637
  .sa-tenants__icon-btn--accent {
698
638
  color: var(--sa-accent, #7c3aed);
699
639
  }
700
-
701
- .sa-tenants__empty {
702
- padding: 40px 16px;
703
- text-align: center;
704
- color: var(--sa-muted, #64748b);
705
- }
706
- .sa-tenants__loading {
707
- padding: 24px;
708
- display: flex;
709
- align-items: center;
710
- gap: 12px;
711
- color: var(--sa-muted, #64748b);
712
- }
713
-
714
- .sa-tenants__foot {
715
- padding: 0;
716
- text-align: right;
717
- font-size: 12px;
718
- color: var(--sa-muted, #64748b);
719
- }
720
640
  </style>