@saasicat/ui-vue 0.21.0 → 0.22.1

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
@@ -15,7 +15,7 @@
15
15
  </q-banner>
16
16
 
17
17
  <!-- Step 1: create SuperAdmin -->
18
- <q-form v-if="step === 'form'" @submit.prevent="submitCreate" class="sa-setup-form">
18
+ <q-form v-if="step === 'form'" class="sa-setup-form" @submit.prevent="submitCreate">
19
19
  <p class="sa-setup-hint">
20
20
  {{ msg.setup.tokenHintBefore }}
21
21
  <code>SETUP_TOKEN</code>
@@ -104,7 +104,7 @@
104
104
  <code class="sa-setup-uri__value">{{ result.otpauthUri }}</code>
105
105
  </details>
106
106
 
107
- <q-form @submit.prevent="submitConfirm" class="sa-setup-form q-mt-md">
107
+ <q-form class="sa-setup-form q-mt-md" @submit.prevent="submitConfirm">
108
108
  <q-input
109
109
  v-model="mfaCode"
110
110
  :label="msg.setup.codeLabel"
@@ -216,9 +216,12 @@ async function postJson<T>(path: string, body: unknown): Promise<T> {
216
216
  return await httpPostJson<T>(http, `${endpoints.apiBase}${path}`, body);
217
217
  } catch (err) {
218
218
  if (err instanceof HttpJsonError) {
219
+ // The message is translated for the user; `cause` keeps the status
220
+ // and the machine-readable code reachable for anything upstream.
219
221
  throw new Error(
220
222
  (err.code && errorByCode.value[err.code]) ||
221
223
  formatMessage(msg.value.setup.errorHttp, { status: err.status }),
224
+ { cause: err },
222
225
  );
223
226
  }
224
227
  throw err;
@@ -274,7 +277,7 @@ async function submitConfirm(): Promise<void> {
274
277
  align-items: center;
275
278
  justify-content: center;
276
279
  padding: 24px;
277
- background: linear-gradient(180deg, #0f172a 0%, #1e293b 100%);
280
+ background: linear-gradient(180deg, var(--sa-heading) 0%, #1e293b 100%);
278
281
  }
279
282
  .sa-setup-card {
280
283
  width: 460px;
@@ -293,7 +296,7 @@ async function submitConfirm(): Promise<void> {
293
296
  .sa-setup-locale {
294
297
  margin-left: auto;
295
298
  align-self: flex-start;
296
- color: #475569;
299
+ color: var(--sa-muted-dark);
297
300
  }
298
301
  .sa-setup-badge {
299
302
  width: 44px;
@@ -302,7 +305,7 @@ async function submitConfirm(): Promise<void> {
302
305
  display: flex;
303
306
  align-items: center;
304
307
  justify-content: center;
305
- background: linear-gradient(135deg, #475569, #1e293b);
308
+ background: linear-gradient(135deg, var(--sa-muted-dark), #1e293b);
306
309
  color: #fff;
307
310
  font-weight: 800;
308
311
  font-size: 18px;
@@ -312,21 +315,21 @@ async function submitConfirm(): Promise<void> {
312
315
  margin: 0;
313
316
  font-weight: 800;
314
317
  font-size: 18px;
315
- color: #0f172a;
318
+ color: var(--sa-heading);
316
319
  line-height: 1.1;
317
320
  }
318
321
  .sa-setup-sub {
319
322
  font-size: 12px;
320
- color: #475569;
323
+ color: var(--sa-muted-dark);
321
324
  }
322
325
  .sa-setup-hint {
323
326
  font-size: 13px;
324
- color: #475569;
327
+ color: var(--sa-muted-dark);
325
328
  line-height: 1.5;
326
329
  margin: 0 0 16px;
327
330
  }
328
331
  .sa-setup-hint code {
329
- background: #e2e8f0;
332
+ background: var(--sa-border);
330
333
  padding: 1px 5px;
331
334
  border-radius: 4px;
332
335
  }
@@ -354,17 +357,17 @@ async function submitConfirm(): Promise<void> {
354
357
  margin-bottom: 12px;
355
358
  }
356
359
  .sa-setup-qr img {
357
- border: 1px solid #e2e8f0;
360
+ border: 1px solid var(--sa-border);
358
361
  border-radius: 8px;
359
362
  padding: 8px;
360
363
  background: #fff;
361
364
  }
362
365
  .sa-setup-qr__hint {
363
366
  font-size: 12px;
364
- color: #64748b;
367
+ color: var(--sa-muted);
365
368
  }
366
369
  .sa-setup-secret {
367
- background: #f1f5f9;
370
+ background: var(--sa-border-soft);
368
371
  border-radius: 8px;
369
372
  padding: 10px 12px;
370
373
  margin-bottom: 10px;
@@ -373,27 +376,27 @@ async function submitConfirm(): Promise<void> {
373
376
  font-size: 11px;
374
377
  text-transform: uppercase;
375
378
  letter-spacing: 0.06em;
376
- color: #64748b;
379
+ color: var(--sa-muted);
377
380
  margin-bottom: 4px;
378
381
  }
379
382
  .sa-setup-secret__value {
380
383
  font-size: 15px;
381
384
  font-weight: 700;
382
- color: #0f172a;
385
+ color: var(--sa-heading);
383
386
  word-break: break-all;
384
387
  letter-spacing: 0.04em;
385
388
  }
386
389
  .sa-setup-uri {
387
390
  margin: 4px 0 4px;
388
391
  font-size: 12px;
389
- color: #475569;
392
+ color: var(--sa-muted-dark);
390
393
  }
391
394
  .sa-setup-uri__value {
392
395
  display: block;
393
396
  margin-top: 6px;
394
397
  word-break: break-all;
395
398
  color: #334155;
396
- background: #f8fafc;
399
+ background: var(--sa-bg-surface-2);
397
400
  padding: 6px 8px;
398
401
  border-radius: 6px;
399
402
  }
@@ -402,7 +405,7 @@ async function submitConfirm(): Promise<void> {
402
405
  width: 100%;
403
406
  background: none;
404
407
  border: none;
405
- color: #64748b;
408
+ color: var(--sa-muted);
406
409
  font-size: 12px;
407
410
  cursor: pointer;
408
411
  text-decoration: underline;
@@ -1,20 +1,32 @@
1
1
  <template>
2
2
  <AdminPage class="sa-tenant-detail">
3
3
  <AdminHero :title="data?.name ?? labels.title">
4
- <template #before-title>
5
- <q-btn
6
- flat
7
- dense
8
- icon="arrow_back"
9
- :label="labels.back"
10
- :to="backRoute"
11
- class="sa-tenant-detail__back"
12
- />
13
- </template>
14
4
  <template v-if="data" #subtitle>
15
5
  {{ labels.slug }}: <code>{{ data.slug }}</code>
16
6
  </template>
7
+ <!--
8
+ Back first, then the tenant-level actions, then whatever the app
9
+ adds — the same order every other hero uses. Both are plain
10
+ `.sa-btn` buttons: a hero is where a page's own actions live, and
11
+ a detail page's "suspend" is exactly that. It used to sit on the
12
+ master-data section instead, which read as an action on that one
13
+ card rather than on the tenant.
14
+ -->
17
15
  <template #actions>
16
+ <button class="sa-btn" type="button" @click="goBack">
17
+ <q-icon name="arrow_back" size="16px" />
18
+ <span>{{ labels.back }}</span>
19
+ </button>
20
+ <button
21
+ v-for="action in manifestActions"
22
+ :key="action.def.id"
23
+ :class="['sa-btn', toneClass(action.def.actionKey)]"
24
+ type="button"
25
+ @click="action.onClick"
26
+ >
27
+ <q-icon :name="iconForActionKey(action.def.actionKey)" size="16px" />
28
+ <span>{{ action.def.label }}</span>
29
+ </button>
18
30
  <slot name="header-actions" :data="data" :reload="load" />
19
31
  </template>
20
32
  </AdminHero>
@@ -22,29 +34,15 @@
22
34
  <AdminBody :loading="loading" :empty="!data">
23
35
  <template v-if="data">
24
36
  <!-- Master data -->
25
- <AdminSection
26
- :title="labels.masterData"
27
- :subtitle="stammdatenSub"
28
- class="sa-card q-mb-md"
29
- >
37
+ <AdminSection :title="labels.masterData" :subtitle="stammdatenSub" class="q-mb-md">
30
38
  <template #actions>
31
- <!-- Manifest-driven default actions (Suspend/Reactivate) -->
32
- <q-btn
33
- v-for="action in manifestActions"
34
- :key="action.def.id"
35
- outline
36
- :color="toneColor(action.def.actionKey)"
37
- :icon="iconForActionKey(action.def.actionKey)"
38
- :label="action.def.label"
39
- @click="action.onClick"
40
- />
41
39
  <slot name="card-actions" :data="data" :reload="load" />
42
40
  </template>
43
41
  <slot name="stammdaten" :data="data">
44
42
  <TenantMasterData
45
43
  :data="data"
46
44
  :labels="labels"
47
- :format-date="formatDate"
45
+ :format-date="formatDateResolved"
48
46
  :yes="common.yes"
49
47
  :no="common.no"
50
48
  >
@@ -57,17 +55,13 @@
57
55
  <AdminSection
58
56
  v-if="verbrauchFields.length > 0"
59
57
  :title="labels.usage"
60
- class="sa-card q-mb-md"
58
+ class="q-mb-md"
61
59
  >
62
60
  <TenantUsage :data="data" :fields="verbrauchFields" />
63
61
  </AdminSection>
64
62
 
65
63
  <!-- Users -->
66
- <AdminSection
67
- v-if="showUsers && data.users"
68
- :title="labels.users"
69
- class="sa-card q-mb-md"
70
- >
64
+ <AdminSection v-if="showUsers && data.users" :title="labels.users" class="q-mb-md">
71
65
  <TenantUsers :users="data.users" :columns="userColumns ?? defaultUserColumns" />
72
66
  </AdminSection>
73
67
 
@@ -101,7 +95,7 @@ import { useMfaPrompt } from '../vue/use-mfa-prompt.js';
101
95
  import AdminHero from '../components/admin-page/AdminHero.vue';
102
96
  import AdminSection from '../components/admin-page/AdminSection.vue';
103
97
  import AdminPage from '../components/admin-page/AdminPage.vue';
104
- import type { RouteLocationRaw } from 'vue-router';
98
+ import { useRouter, type RouteLocationRaw } from 'vue-router';
105
99
  import type { TenantDetailData, VerbrauchField } from './tenant-detail/types.js';
106
100
  import type { QTableColumn } from 'quasar';
107
101
  import { useSuperAdminNotify } from '../quasar/notify.js';
@@ -189,7 +183,7 @@ function defaultFormatDate(value: string | null | undefined): string {
189
183
  return String(value).slice(0, 10);
190
184
  }
191
185
 
192
- function formatDate(value: string | null | undefined): string {
186
+ function formatDateResolved(value: string | null | undefined): string {
193
187
  return props.formatDate ? props.formatDate(value) : defaultFormatDate(value);
194
188
  }
195
189
 
@@ -272,16 +266,29 @@ const manifestActions = computed(() => {
272
266
  .map((a) => ({ def: a.def, onClick: () => void a.invoke(row) }));
273
267
  });
274
268
 
269
+ const router = useRouter();
270
+
271
+ /**
272
+ * The hero back button navigates rather than rendering a link.
273
+ *
274
+ * `backRoute` is a `RouteLocationRaw`, not an href, so there is no anchor to
275
+ * build without duplicating the router's resolution — and every other hero in
276
+ * the package uses a button for the same step.
277
+ */
278
+ function goBack(): void {
279
+ void router.push(props.backRoute);
280
+ }
281
+
275
282
  function iconForActionKey(actionKey: string): string {
276
283
  if (actionKey.endsWith('.suspend')) return 'block';
277
284
  if (actionKey.endsWith('.reactivate')) return 'play_arrow';
278
285
  return 'bolt';
279
286
  }
280
287
 
281
- function toneColor(actionKey: string): string {
282
- if (actionKey.endsWith('.suspend')) return 'negative';
283
- if (actionKey.endsWith('.reactivate')) return 'positive';
284
- return 'primary';
288
+ function toneClass(actionKey: string): string {
289
+ if (actionKey.endsWith('.suspend')) return 'sa-btn--danger';
290
+ if (actionKey.endsWith('.reactivate')) return 'sa-btn--positive';
291
+ return 'sa-btn--primary';
285
292
  }
286
293
 
287
294
  const defaultUserColumns = computed<QTableColumn[]>(() => [
@@ -316,9 +323,6 @@ const defaultUserColumns = computed<QTableColumn[]>(() => [
316
323
  </script>
317
324
 
318
325
  <style scoped>
319
- .sa-tenant-detail__back {
320
- margin-bottom: 6px;
321
- }
322
326
  .sa-tenant-detail__card-head {
323
327
  display: flex;
324
328
  justify-content: space-between;