@saasicat/ui-vue 0.4.0 → 0.6.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 (214) hide show
  1. package/README.md +64 -4
  2. package/dist/chunk-A3W2N26K.js +714 -0
  3. package/dist/chunk-QZOSDCNP.js +98 -0
  4. package/dist/chunk-X5SQ5HMB.js +3231 -0
  5. package/dist/client/index.cjs +3988 -0
  6. package/dist/client/index.d.cts +321 -0
  7. package/dist/client/index.d.ts +321 -0
  8. package/dist/client/index.js +70 -0
  9. package/dist/index.cjs +4446 -1249
  10. package/dist/index.d.cts +297 -888
  11. package/dist/index.d.ts +297 -888
  12. package/dist/index.js +609 -1348
  13. package/dist/messages-7b0P8W75.d.cts +1652 -0
  14. package/dist/messages-7b0P8W75.d.ts +1652 -0
  15. package/dist/quasar/index.cjs +3340 -0
  16. package/dist/quasar/index.d.cts +95 -0
  17. package/dist/quasar/index.d.ts +95 -0
  18. package/dist/quasar/index.js +96 -0
  19. package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
  20. package/dist/testing-e2e/admin-pages-suite.js +14 -11
  21. package/dist/use-super-admin-i18n-B3v3-SWZ.d.ts +210 -0
  22. package/dist/use-super-admin-i18n-DMktRVEt.d.cts +210 -0
  23. package/package.json +23 -3
  24. package/src/{action-registry.ts → client/action-registry.ts} +4 -4
  25. package/src/{batch-column-fetcher.ts → client/batch-column-fetcher.ts} +4 -4
  26. package/src/{boot-loader.ts → client/boot-loader.ts} +3 -3
  27. package/src/client/i18n/currency.ts +42 -0
  28. package/src/client/i18n/define.ts +59 -0
  29. package/src/client/i18n/format.ts +12 -0
  30. package/src/client/i18n/index.ts +14 -0
  31. package/src/client/i18n/locale.ts +15 -0
  32. package/src/client/i18n/messages/audit.ts +42 -0
  33. package/src/client/i18n/messages/bundles.ts +459 -0
  34. package/src/client/i18n/messages/common.ts +147 -0
  35. package/src/client/i18n/messages/dashboard.ts +16 -0
  36. package/src/client/i18n/messages/discovery.ts +229 -0
  37. package/src/client/i18n/messages/email.ts +125 -0
  38. package/src/client/i18n/messages/marketing.ts +270 -0
  39. package/src/client/i18n/messages/nav.ts +72 -0
  40. package/src/client/i18n/messages/pilots.ts +192 -0
  41. package/src/client/i18n/messages/plan-detail.ts +266 -0
  42. package/src/client/i18n/messages/plan-editor.ts +177 -0
  43. package/src/client/i18n/messages/plan-versions.ts +251 -0
  44. package/src/client/i18n/messages/plans.ts +503 -0
  45. package/src/client/i18n/messages/promos.ts +202 -0
  46. package/src/client/i18n/messages/shell.ts +145 -0
  47. package/src/client/i18n/messages/tenants.ts +104 -0
  48. package/src/client/i18n/messages/users.ts +78 -0
  49. package/src/client/i18n/messages.ts +84 -0
  50. package/src/client/index.ts +27 -0
  51. package/src/{manifest-loader.ts → client/manifest-loader.ts} +4 -4
  52. package/src/{nav-builder.ts → client/nav-builder.ts} +44 -51
  53. package/src/{plan-versions-catalog.ts → client/plan-versions-catalog.ts} +42 -30
  54. package/src/components/BundleVersionPublishDialog.vue +35 -24
  55. package/src/components/FeatureGate.vue +1 -1
  56. package/src/components/MarketingPromotionsTab.vue +112 -59
  57. package/src/components/MfaPromptDialog.vue +11 -11
  58. package/src/components/TenantActionConfirmDialog.vue +19 -17
  59. package/src/components/VersionDiffPreview.vue +72 -43
  60. package/src/components/bundle-editor/BundleCreatePanel.vue +76 -63
  61. package/src/components/bundle-editor/BundleFeaturesEditor.vue +13 -6
  62. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +19 -14
  63. package/src/components/bundle-editor/BundleQuotasEditor.vue +7 -4
  64. package/src/components/bundle-editor/BundleStatusBanner.vue +36 -19
  65. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +51 -35
  66. package/src/components/bundle-editor/BundleVersionStrip.vue +24 -12
  67. package/src/components/bundle-editor/bundle-version-status.ts +29 -29
  68. package/src/components/dialogs/PilotCreateDialog.vue +58 -63
  69. package/src/components/dialogs/PilotEditDialog.vue +40 -34
  70. package/src/components/dialogs/PromoCodeCreateDialog.vue +89 -63
  71. package/src/components/dialogs/PromoCodeEditDialog.vue +98 -66
  72. package/src/components/dialogs/types.ts +3 -13
  73. package/src/components/plan/PlanCycleToggle.vue +21 -10
  74. package/src/components/plan/PlanGrid.vue +2 -2
  75. package/src/components/plan/PriceSummary.vue +1 -1
  76. package/src/components/plan/PromoCodeInput.vue +1 -1
  77. package/src/components/plan/PublicBundleGrid.vue +1 -1
  78. package/src/components/plan-cockpit/PlanCockpit.vue +34 -15
  79. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +13 -6
  80. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +18 -9
  81. package/src/components/plan-cockpit/PlanCockpitHeader.vue +9 -6
  82. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +24 -11
  83. package/src/components/plan-cockpit/PlanCockpitKpis.vue +28 -17
  84. package/src/components/plan-cockpit/PlanCockpitVersions.vue +50 -25
  85. package/src/components/plan-create-dialog/PlanCreateDialog.vue +45 -35
  86. package/src/components/plan-detail/PlanAuditLog.vue +11 -6
  87. package/src/components/plan-detail/PlanDetail.vue +21 -23
  88. package/src/components/plan-detail/PlanDetailHeader.vue +20 -11
  89. package/src/components/plan-detail/PlanDetailKpis.vue +30 -17
  90. package/src/components/plan-detail/PlanTerminateDialog.vue +22 -9
  91. package/src/components/plan-detail/PlanVersionDiffPanel.vue +61 -27
  92. package/src/components/plan-detail/PlanVersionsPanel.vue +71 -36
  93. package/src/components/plan-list/PlanList.vue +99 -57
  94. package/src/components/plan-matrix/PlanMatrix.vue +85 -38
  95. package/src/components/plan-review/PlanReview.vue +105 -80
  96. package/src/components/plan-version-editor/PlanCatalogPreview.vue +43 -20
  97. package/src/components/plan-version-editor/PlanComponentPool.vue +15 -7
  98. package/src/components/plan-version-editor/PlanVersionBasket.vue +45 -24
  99. package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +8 -7
  100. package/src/components/plan-version-editor/PlanVersionEditor.vue +74 -40
  101. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +24 -12
  102. package/src/index.ts +82 -79
  103. package/src/pages-standard/AdminLayout.vue +43 -18
  104. package/src/pages-standard/AdminManifestErrorPage.vue +10 -10
  105. package/src/pages-standard/AuditPage.vue +58 -19
  106. package/src/pages-standard/BundlesPage.vue +29 -18
  107. package/src/pages-standard/DashboardPage.vue +31 -17
  108. package/src/pages-standard/DiscoveryPage.vue +40 -28
  109. package/src/pages-standard/EmailHistoryPage.vue +98 -69
  110. package/src/pages-standard/MarketingCatalogPage.vue +40 -21
  111. package/src/pages-standard/PilotsPage.vue +64 -52
  112. package/src/pages-standard/PlanVersionsPage.vue +9 -5
  113. package/src/pages-standard/PlansPage.vue +91 -59
  114. package/src/pages-standard/PlatformEmailPage.vue +120 -53
  115. package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
  116. package/src/pages-standard/PromoCodesPage.vue +51 -54
  117. package/src/pages-standard/SubscriptionsPage.vue +14 -9
  118. package/src/pages-standard/SuperAdminLoginPage.vue +19 -16
  119. package/src/pages-standard/SuperAdminSetupWizard.vue +53 -49
  120. package/src/pages-standard/TenantDetailPage.vue +54 -40
  121. package/src/pages-standard/TenantsPage.vue +57 -44
  122. package/src/pages-standard/UsersPage.vue +70 -52
  123. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -8
  124. package/src/pages-standard/bundles-page/BundleDetailPanel.vue +28 -18
  125. package/src/pages-standard/bundles-page/BundlesFilterBar.vue +4 -1
  126. package/src/pages-standard/bundles-page/BundlesHeader.vue +9 -8
  127. package/src/pages-standard/bundles-page/BundlesKpis.vue +29 -11
  128. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +20 -8
  129. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -3
  130. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +43 -24
  131. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +7 -4
  132. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +28 -15
  133. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +20 -10
  134. package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +9 -6
  135. package/src/pages-standard/discovery-page/discovery-ui.ts +40 -52
  136. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +81 -49
  137. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +13 -10
  138. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +24 -20
  139. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +6 -3
  140. package/src/pages-standard/plan-versions/PlanDiffCard.vue +16 -6
  141. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +42 -26
  142. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +46 -27
  143. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +8 -6
  144. package/src/pages-standard/plan-versions/PlanVersionsList.vue +40 -17
  145. package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +1 -1
  146. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +25 -13
  147. package/src/pages-standard/plan-versions/format.ts +33 -17
  148. package/src/pages-standard/plans-page/PlanArchiveDialog.vue +12 -8
  149. package/src/pages-standard/plans-page/PlanBundleOverview.vue +15 -6
  150. package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +18 -7
  151. package/src/pages-standard/plans-page/PlanPublishDialog.vue +23 -12
  152. package/src/pages-standard/tenants/format.ts +3 -2
  153. package/src/pages-tenant/MySubscriptionBundlesPage.vue +73 -38
  154. package/src/pages-tenant/OnboardingConfigurator.vue +3 -3
  155. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  156. package/src/pages-tenant/PlanChangeWizard.vue +10 -6
  157. package/src/pages-tenant/TenantPlanSection.vue +20 -16
  158. package/src/pages-tenant/default-i18n.ts +217 -1
  159. package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +9 -7
  160. package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +11 -12
  161. package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
  162. package/src/pages-tenant/tenant-plan-section/TenantPlanCardHeader.vue +1 -1
  163. package/src/pages-tenant/tenant-plan-section/TenantUsageGrid.vue +1 -1
  164. package/src/quasar/create-super-admin-app.ts +192 -0
  165. package/src/quasar/index.ts +11 -0
  166. package/src/quasar/notify.ts +30 -0
  167. package/src/testing-e2e/admin-pages-suite.ts +14 -11
  168. package/src/{manifest-store-factory.ts → vue/manifest-store-factory.ts} +1 -1
  169. package/src/{platform-loaders.ts → vue/platform-loaders.ts} +4 -4
  170. package/src/{project-page-host.ts → vue/project-page-host.ts} +6 -4
  171. package/src/{create-super-admin-app.ts → vue/super-admin-context.ts} +20 -139
  172. package/src/vue/ui-notify.ts +30 -0
  173. package/src/{use-actions.ts → vue/use-actions.ts} +1 -1
  174. package/src/{use-api-list.ts → vue/use-api-list.ts} +1 -1
  175. package/src/{use-audit-entries.ts → vue/use-audit-entries.ts} +3 -3
  176. package/src/{use-batch-columns.ts → vue/use-batch-columns.ts} +1 -1
  177. package/src/{use-bulk-publish.ts → vue/use-bulk-publish.ts} +7 -5
  178. package/src/{use-bundle-versions-map.ts → vue/use-bundle-versions-map.ts} +7 -5
  179. package/src/{use-bundles.ts → vue/use-bundles.ts} +14 -14
  180. package/src/{use-catalog-entries.ts → vue/use-catalog-entries.ts} +14 -11
  181. package/src/{use-discovery.ts → vue/use-discovery.ts} +9 -6
  182. package/src/{use-entitlement.ts → vue/use-entitlement.ts} +4 -4
  183. package/src/{use-live-plan-versions.ts → vue/use-live-plan-versions.ts} +5 -5
  184. package/src/{use-manifest.ts → vue/use-manifest.ts} +1 -1
  185. package/src/{use-marketing-projections.ts → vue/use-marketing-projections.ts} +10 -7
  186. package/src/{use-nav.ts → vue/use-nav.ts} +9 -3
  187. package/src/{use-plan-editor.ts → vue/use-plan-editor.ts} +2 -2
  188. package/src/{use-plan-versions.ts → vue/use-plan-versions.ts} +3 -3
  189. package/src/{use-plans.ts → vue/use-plans.ts} +13 -13
  190. package/src/{use-platform-tenant-actions.ts → vue/use-platform-tenant-actions.ts} +9 -6
  191. package/src/{use-promotions.ts → vue/use-promotions.ts} +10 -11
  192. package/src/{use-public-boot.ts → vue/use-public-boot.ts} +1 -1
  193. package/src/{use-subscription-draft.ts → vue/use-subscription-draft.ts} +2 -3
  194. package/src/{use-super-admin-context.ts → vue/use-super-admin-context.ts} +5 -5
  195. package/src/vue/use-super-admin-i18n.ts +82 -0
  196. package/src/{use-tenant-action-flow.ts → vue/use-tenant-action-flow.ts} +12 -7
  197. package/src/{use-tenant-billing-catalog.ts → vue/use-tenant-billing-catalog.ts} +2 -2
  198. package/src/{use-tenant-billing.ts → vue/use-tenant-billing.ts} +2 -2
  199. package/src/{use-tenant-manifest.ts → vue/use-tenant-manifest.ts} +2 -4
  200. package/src/{use-tenant-subscription-bundles.ts → vue/use-tenant-subscription-bundles.ts} +5 -5
  201. package/src/{use-tenants.ts → vue/use-tenants.ts} +3 -3
  202. package/dist/testing-e2e/admin-pages-suite.d.cts +0 -37
  203. package/dist/testing-e2e/admin-pages-suite.d.ts +0 -37
  204. package/src/components/BusinessTypeVersionEditorDialog.vue +0 -417
  205. package/src/components/BusinessTypeVersionPublishDialog.vue +0 -258
  206. package/src/pages-standard/BusinessTypesPage.vue +0 -662
  207. package/src/pages-standard/plan-versions/VersionDiffPreview.vue +0 -167
  208. package/src/use-business-types.ts +0 -269
  209. /package/src/{http-json.ts → client/http-json.ts} +0 -0
  210. /package/src/{types.ts → client/types.ts} +0 -0
  211. /package/src/{version.ts → client/version.ts} +0 -0
  212. /package/src/{create-admin-routes.ts → vue/create-admin-routes.ts} +0 -0
  213. /package/src/{entitlement-provider.ts → vue/entitlement-provider.ts} +0 -0
  214. /package/src/{feature-router-guard.ts → vue/feature-router-guard.ts} +0 -0
@@ -109,7 +109,7 @@ export function runAdminPagesSuite(
109
109
 
110
110
  test.describe(`${config.appName}: SuperAdmin-Pages Smoke`, () => {
111
111
  for (const pageDef of config.pages) {
112
- test(`${pageDef.name} rendert ohne Fehler`, async ({ page }) => {
112
+ test(`${pageDef.name} renders without errors`, async ({ page }) => {
113
113
  const errors = attachListeners(
114
114
  page,
115
115
  config.consoleErrorAllowlist,
@@ -127,19 +127,19 @@ export function runAdminPagesSuite(
127
127
  timeout: 5_000,
128
128
  });
129
129
  }
130
- expect(errors.pageErrors, `pageerror-Events auf ${pageDef.path}`).toHaveLength(0);
130
+ expect(errors.pageErrors, `pageerror events on ${pageDef.path}`).toHaveLength(0);
131
131
  expect(
132
132
  errors.consoleErrors,
133
- `console.error-Events auf ${pageDef.path}`,
133
+ `console.error events on ${pageDef.path}`,
134
134
  ).toHaveLength(0);
135
135
  expect(
136
136
  errors.networkErrors,
137
- `HTTP-Errors auf /api/ während ${pageDef.path}`,
137
+ `HTTP errors on /api/ during ${pageDef.path}`,
138
138
  ).toHaveLength(0);
139
139
  });
140
140
  }
141
141
 
142
- test('Dashboard zeigt erwartete KPIs, Distributions und Shortcuts', async ({ page }) => {
142
+ test('Dashboard shows the expected KPIs, distributions and shortcuts', async ({ page }) => {
143
143
  attachListeners(page, config.consoleErrorAllowlist);
144
144
  await loginIfNeeded(page, config);
145
145
  const origin = new URL(config.loginUrl).origin;
@@ -151,7 +151,9 @@ export function runAdminPagesSuite(
151
151
  const kpiLabels = await page.locator('.sa-kpi__label').allTextContents();
152
152
  const normalizedKpis = kpiLabels.map((s) => s.trim());
153
153
  for (const expected of config.expectedKpiLabels) {
154
- expect(normalizedKpis, `KPI '${expected}' fehlt auf Dashboard`).toContain(expected);
154
+ expect(normalizedKpis, `KPI '${expected}' missing on the dashboard`).toContain(
155
+ expected,
156
+ );
155
157
  }
156
158
 
157
159
  if (config.expectedDistributionTitles?.length) {
@@ -162,7 +164,7 @@ export function runAdminPagesSuite(
162
164
  for (const expected of config.expectedDistributionTitles) {
163
165
  expect(
164
166
  normalizedDist,
165
- `Distribution '${expected}' fehlt auf Dashboard`,
167
+ `Distribution '${expected}' missing on the dashboard`,
166
168
  ).toContain(expected);
167
169
  }
168
170
  }
@@ -172,14 +174,15 @@ export function runAdminPagesSuite(
172
174
  .allTextContents();
173
175
  const normalizedShortcuts = shortcutTitles.map((s) => s.trim());
174
176
  for (const expected of config.expectedShortcutTitles) {
175
- expect(normalizedShortcuts, `Shortcut '${expected}' fehlt auf Dashboard`).toContain(
176
- expected,
177
- );
177
+ expect(
178
+ normalizedShortcuts,
179
+ `Shortcut '${expected}' missing on the dashboard`,
180
+ ).toContain(expected);
178
181
  }
179
182
  for (const forbidden of config.forbiddenShortcutTitles ?? []) {
180
183
  expect(
181
184
  normalizedShortcuts,
182
- `Shortcut '${forbidden}' darf nicht auf Dashboard sein`,
185
+ `Shortcut '${forbidden}' must not be on the dashboard`,
183
186
  ).not.toContain(forbidden);
184
187
  }
185
188
  });
@@ -13,7 +13,7 @@
13
13
  import { defineStore, type StoreDefinition } from 'pinia';
14
14
  import { ref, type Ref } from 'vue';
15
15
  import type { AdminManifest } from '@saasicat/types';
16
- import type { ManifestLoader } from './manifest-loader.js';
16
+ import type { ManifestLoader } from '../client/manifest-loader.js';
17
17
 
18
18
  export interface ManifestStoreState {
19
19
  manifest: Ref<AdminManifest | null>;
@@ -5,10 +5,10 @@
5
5
  // a single constant that `main.ts` passes to `createSuperAdminApp()` and that
6
6
  // the loader wiring from this helper consumes.
7
7
 
8
- import { BootLoader } from './boot-loader.js';
9
- import { ManifestLoader } from './manifest-loader.js';
10
- import type { SuperAdminEndpoints } from './create-super-admin-app.js';
11
- import type { HttpClient, KvStore } from './types.js';
8
+ import { BootLoader } from '../client/boot-loader.js';
9
+ import { ManifestLoader } from '../client/manifest-loader.js';
10
+ import type { SuperAdminEndpoints } from './super-admin-context.js';
11
+ import type { HttpClient, KvStore } from '../client/types.js';
12
12
 
13
13
  export interface CreatePlatformLoadersOptions {
14
14
  /**
@@ -14,7 +14,7 @@ import {
14
14
  SUPER_ADMIN_EXTENSIONS_KEY,
15
15
  SUPER_ADMIN_MANIFEST_KEY,
16
16
  type ExtensionsMap,
17
- } from './create-super-admin-app.js';
17
+ } from './super-admin-context.js';
18
18
 
19
19
  // `defineAsyncComponent` gets its own wrapper component per loader
20
20
  // function — without a cache, re-renders would recreate the same component
@@ -60,13 +60,15 @@ export const ProjectPageHost = defineComponent({
60
60
  const page = matchingPage.value;
61
61
  const cmp = resolvedComponent.value;
62
62
  if (page && cmp) return h(cmp);
63
+ // Misconfiguration diagnostics — developer-facing, therefore not
64
+ // part of the localized catalog.
63
65
  return h('div', { class: 'sa-project-page-host__missing', role: 'alert' }, [
64
- h('h2', 'Seite nicht verfügbar'),
66
+ h('h2', 'Page not available'),
65
67
  h(
66
68
  'p',
67
69
  page
68
- ? `componentKey "${page.componentKey}" ist nicht in der extensions:-Map registriert. Apps müssen die Komponente in createSuperAdminApp({ extensions }) eintragen.`
69
- : `Keine Manifest-Definition für ${route.path}. Entweder fehlt die ProjectPage im Manifest, oder die Capability ist nicht aktiv.`,
70
+ ? `componentKey "${page.componentKey}" is not registered in the extensions: map. Apps must register the component in createSuperAdminApp({ extensions }).`
71
+ : `No manifest definition for ${route.path}. Either the ProjectPage is missing from the manifest, or the capability is not active.`,
70
72
  ),
71
73
  ]);
72
74
  };
@@ -1,29 +1,17 @@
1
- // createSuperAdminAppuniversal bootstrap helper for SuperAdmin Vue apps.
2
- // Bundles the Quasar/Pinia/Router setup that every consumer app rebuilds itself
3
- // today, and exposes the platform contract maps (`extensions:`, `actions:`)
4
- // via `provide()`.
1
+ // SuperAdmin shell contract injection keys, option types and the
2
+ // navigation guard shared between the Quasar bootstrap
3
+ // (`createSuperAdminApp()`, `@saasicat/ui-vue/quasar`) and the framework
4
+ // components/composables in this package.
5
5
  //
6
- // What the helper does NOT do:
7
- // - Auth stores, login flows, JWT refresh — app-specific.
8
- // - CSS imports (Quasar CSS, Material Icons, app theme) — these stay in the
9
- // app's own `main.ts`, because tsup does not bundle CSS.
6
+ // Lives in the Vue layer (no Quasar import) so that the main entry and all
7
+ // composables stay consumable without Quasar being installed.
10
8
 
11
- import { createApp, type App, type Component, type InjectionKey } from 'vue';
12
- import { Quasar, Notify, Dialog, Loading, type QuasarPluginOptions } from 'quasar';
13
- import { createPinia, type Pinia } from 'pinia';
14
- import {
15
- createRouter,
16
- createWebHistory,
17
- type NavigationGuardWithThis,
18
- type RouteLocationNormalized,
19
- type RouteRecordRaw,
20
- type Router,
21
- type RouterHistory,
22
- } from 'vue-router';
9
+ import type { App, Component, InjectionKey } from 'vue';
10
+ import type { NavigationGuardWithThis, RouteLocationNormalized } from 'vue-router';
23
11
 
24
12
  import type { ActionKey, AdminManifest, ComponentKey } from '@saasicat/types';
25
- import type { ActionHandler } from './action-registry.js';
26
- import { defaultHttpClient, type HttpClient } from './types.js';
13
+ import type { ActionHandler } from '../client/action-registry.js';
14
+ import type { HttpClient } from '../client/types.js';
27
15
 
28
16
  /**
29
17
  * App-specific branding data that the platform `AdminLayout` and other
@@ -133,25 +121,11 @@ export interface SuperAdminManifestGuardOptions {
133
121
  errorRoute?: string;
134
122
  }
135
123
 
136
- export interface CreateSuperAdminAppOptions {
137
- /** App root component (`App.vue`). */
138
- rootComponent: Component;
139
- /** App branding (logo, name). */
140
- brand: SuperAdminBrand;
141
- /** Endpoint configuration. */
142
- endpoints: SuperAdminEndpoints;
143
- /** App's own routes (login, standard pages, bundle pages, …). */
144
- appRoutes: RouteRecordRaw[];
145
- /**
146
- * Static `extensions:` map. Manifest `projectPages[].componentKey` is
147
- * looked up in it (see Spec §4.4).
148
- */
149
- extensions?: ExtensionsMap;
150
- /**
151
- * Static `actions:` map. Manifest `tenants.actions[].actionKey` is looked
152
- * up in it.
153
- */
154
- actions?: ActionsMap;
124
+ /**
125
+ * Guard configuration shared by `buildNavigationGuard()` and
126
+ * `createSuperAdminApp()` (which embeds both fields in its options).
127
+ */
128
+ export interface SuperAdminGuardOptions {
155
129
  /**
156
130
  * Optional: auth guard. When set, `router.beforeEach` is wired up
157
131
  * automatically — `to.meta.public === true` bypasses the guard.
@@ -162,47 +136,11 @@ export interface CreateSuperAdminAppOptions {
162
136
  * render until the manifest is loaded (prevents sidebar flicker).
163
137
  */
164
138
  manifestGuard?: SuperAdminManifestGuardOptions;
165
- /**
166
- * Optional: login adapter. When set, it is exposed via `provide()` for the
167
- * shared `<SuperAdminLoginPage>` (from `pages-standard/`) — the app no
168
- * longer needs to hold its own LoginPage Vue component.
169
- */
170
- loginAdapter?: SuperAdminLoginAdapter;
171
- /**
172
- * Optional: router history variant, default `createWebHistory()`.
173
- * Apps with a subpath mount override this.
174
- */
175
- routerHistory?: RouterHistory;
176
- /**
177
- * Optional: Quasar configuration. Default loads `Notify`/`Dialog`/`Loading`
178
- * with the established consumer convention (`top-right`, 3 s).
179
- */
180
- quasarOptions?: Partial<QuasarPluginOptions>;
181
- /**
182
- * Optional: additional Vue plugins (e.g. an app's own NotificationCenter)
183
- * that are installed after the platform setup, before the mount.
184
- */
185
- installPlugins?: Array<(app: App) => void>;
186
- /**
187
- * Optional: `HttpClient` for all pre-login calls (boot, first-run setup).
188
- * Default `defaultHttpClient()` (= `fetch`). Consumers pass their own
189
- * variant through (auth header, baseURL, retry) — it then applies uniformly,
190
- * including for the setup wizard. Consumed via `useSuperAdminHttp()`.
191
- */
192
- http?: HttpClient;
193
- }
194
-
195
- export interface SuperAdminAppHandle {
196
- app: App;
197
- router: Router;
198
- pinia: Pinia;
199
- /** Mounts the app on a selector. Returns the root component instance. */
200
- mount: (selector: string | Element) => ReturnType<App['mount']>;
201
139
  }
202
140
 
203
141
  // Vue inject keys are registered globally via `Symbol.for(...)`. Reason:
204
142
  // apps import `createSuperAdminApp` (and thus `app.provide(KEY, ...)`) from
205
- // the built `dist/index.js` bundle, but shared `.vue` pages in the
143
+ // the built `dist/` bundles, but shared `.vue` pages in the
206
144
  // `pages-standard/` directory directly from `src/`. Both paths produce two
207
145
  // independent module instances — with local `Symbol(...)` the identity would
208
146
  // differ, and `inject()` would always throw "not found". `Symbol.for(...)`
@@ -241,68 +179,11 @@ export const SUPER_ADMIN_HTTP_KEY: InjectionKey<HttpClient> = Symbol.for(
241
179
  '@saasicat/ui-vue/SUPER_ADMIN_HTTP',
242
180
  );
243
181
 
244
- const DEFAULT_QUASAR_OPTIONS: QuasarPluginOptions = {
245
- plugins: { Notify, Dialog, Loading },
246
- config: { notify: { position: 'top-right', timeout: 3000 } },
247
- };
248
-
249
182
  /**
250
- * Universal bootstrap function for SuperAdmin apps. Replaces the `main.ts`
251
- * boilerplate duplicated per app today (Quasar + Pinia + Router + manifest
252
- * guard) and exposes the platform maps via `provide()` for downstream
253
- * components.
183
+ * Additional Vue plugins (e.g. an app's own NotificationCenter) that a
184
+ * bootstrap installs after the platform setup, before the mount.
254
185
  */
255
- export function createSuperAdminApp(options: CreateSuperAdminAppOptions): SuperAdminAppHandle {
256
- const app = createApp(options.rootComponent);
257
-
258
- app.use(Quasar, options.quasarOptions ?? DEFAULT_QUASAR_OPTIONS);
259
-
260
- const pinia = createPinia();
261
- app.use(pinia);
262
-
263
- const router = createRouter({
264
- history: options.routerHistory ?? createWebHistory(),
265
- routes: options.appRoutes,
266
- });
267
-
268
- const navGuard = buildNavigationGuard(options);
269
- if (navGuard) {
270
- router.beforeEach(navGuard);
271
- }
272
-
273
- app.use(router);
274
-
275
- const endpoints: Required<SuperAdminEndpoints> = {
276
- apiBase: options.endpoints.apiBase,
277
- publicBootEndpoint:
278
- options.endpoints.publicBootEndpoint ?? `${options.endpoints.apiBase}/boot`,
279
- manifestEndpoint:
280
- options.endpoints.manifestEndpoint ?? `${options.endpoints.apiBase}/manifest`,
281
- };
282
-
283
- app.provide(SUPER_ADMIN_BRAND_KEY, { tag: 'SuperAdmin', ...options.brand });
284
- app.provide(SUPER_ADMIN_ENDPOINTS_KEY, endpoints);
285
- app.provide(SUPER_ADMIN_EXTENSIONS_KEY, options.extensions ?? {});
286
- app.provide(SUPER_ADMIN_ACTIONS_KEY, options.actions ?? {});
287
- if (options.manifestGuard?.getManifest) {
288
- app.provide(SUPER_ADMIN_MANIFEST_KEY, options.manifestGuard.getManifest);
289
- }
290
- if (options.loginAdapter) {
291
- app.provide(SUPER_ADMIN_LOGIN_ADAPTER_KEY, options.loginAdapter);
292
- }
293
- app.provide(SUPER_ADMIN_HTTP_KEY, options.http ?? defaultHttpClient());
294
-
295
- for (const plugin of options.installPlugins ?? []) {
296
- plugin(app);
297
- }
298
-
299
- return {
300
- app,
301
- router,
302
- pinia,
303
- mount: (selector) => app.mount(selector),
304
- };
305
- }
186
+ export type InstallPlugin = (app: App) => void;
306
187
 
307
188
  /**
308
189
  * Internal helper, exported for isolated unit tests of the navigation
@@ -310,7 +191,7 @@ export function createSuperAdminApp(options: CreateSuperAdminAppOptions): SuperA
310
191
  * `createSuperAdminApp()`, not this helper directly.
311
192
  */
312
193
  export function buildNavigationGuard(
313
- options: Pick<CreateSuperAdminAppOptions, 'authGuard' | 'manifestGuard'>,
194
+ options: SuperAdminGuardOptions,
314
195
  ): NavigationGuardWithThis<undefined> | null {
315
196
  const { authGuard, manifestGuard } = options;
316
197
  if (!authGuard && !manifestGuard) return null;
@@ -0,0 +1,30 @@
1
+ // UI notify port — the single toast/snackbar seam between platform pages and
2
+ // the host app. Standard pages emit user feedback exclusively through this
3
+ // port; which component library renders it is the app's choice.
4
+ //
5
+ // `createSuperAdminApp()` (`@saasicat/ui-vue/quasar`) provides a
6
+ // Quasar-backed default; apps override it via
7
+ // `createSuperAdminApp({ notify })` or by providing the key themselves.
8
+
9
+ import type { InjectionKey } from 'vue';
10
+
11
+ /**
12
+ * Feedback kind, aligned with the established page conventions
13
+ * (`positive` = success, `negative` = error).
14
+ */
15
+ export type UiNotifyKind = 'positive' | 'negative' | 'warning' | 'info';
16
+
17
+ export interface UiNotifyOptions {
18
+ /** Secondary line below the message (e.g. changed fields, one-time password). */
19
+ caption?: string;
20
+ /** Display duration in ms; implementations fall back to their default when unset. */
21
+ timeoutMs?: number;
22
+ }
23
+
24
+ /** Notify port. Compatible with the `(kind, message)` callbacks pages already accept. */
25
+ export type UiNotify = (kind: UiNotifyKind, message: string, options?: UiNotifyOptions) => void;
26
+
27
+ /** Vue inject key for the notify port (see `Symbol.for` note in super-admin-context.ts). */
28
+ export const SUPER_ADMIN_NOTIFY_KEY: InjectionKey<UiNotify> = Symbol.for(
29
+ '@saasicat/ui-vue/SUPER_ADMIN_NOTIFY',
30
+ );
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { computed, type ComputedRef, type Ref } from 'vue';
9
9
  import type { AdminManifest } from '@saasicat/types';
10
- import { ActionRegistry, type ActionHandler } from './action-registry.js';
10
+ import { ActionRegistry, type ActionHandler } from '../client/action-registry.js';
11
11
 
12
12
  export interface UseActionsResult {
13
13
  registry: ComputedRef<ActionRegistry | null>;
@@ -5,7 +5,7 @@
5
5
  // Direct use is also allowed for custom endpoints.
6
6
 
7
7
  import { ref, watch, type Ref } from 'vue';
8
- import { defaultHttpClient, type HttpClient } from './types.js';
8
+ import { defaultHttpClient, type HttpClient } from '../client/types.js';
9
9
 
10
10
  export interface ApiListResponse<T> {
11
11
  items: T[];
@@ -29,9 +29,9 @@ export interface UseAuditEntriesResult extends UseApiListResult<AuditEntry> {
29
29
  export function useAuditEntries(options: UseAuditEntriesOptions): UseAuditEntriesResult {
30
30
  if (!options?.endpoint) {
31
31
  throw new Error(
32
- 'useAuditEntries: `endpoint` ist Pflicht (z. B. "/api/admin/audit" ' +
33
- 'oder "/api/v1/admin/audit"). Plattform hat keinen Default, weil ' +
34
- 'Apps unterschiedliche globalPrefix-Konventionen haben.',
32
+ 'useAuditEntries: `endpoint` is required (e.g. "/api/admin/audit" ' +
33
+ 'or "/api/v1/admin/audit"). The platform has no default because ' +
34
+ 'apps use different globalPrefix conventions.',
35
35
  );
36
36
  }
37
37
  const filter = options.filter ?? ref<AuditQuery>({});
@@ -12,7 +12,7 @@ import {
12
12
  BatchColumnFetcher,
13
13
  type BatchColumnData,
14
14
  type BatchColumnFetcherOptions,
15
- } from './batch-column-fetcher.js';
15
+ } from '../client/batch-column-fetcher.js';
16
16
 
17
17
  export interface UseBatchColumnsResult {
18
18
  /** Data per column key (`columnKey → tenantId → value`). */
@@ -11,7 +11,8 @@
11
11
  // `mfaCode` parameter through.
12
12
 
13
13
  import { computed, ref, type ComputedRef, type Ref } from 'vue';
14
- import { defaultHttpClient, type HttpClient } from './types.js';
14
+ import { defaultHttpClient, type HttpClient } from '../client/types.js';
15
+ import { useSaMessages } from './use-super-admin-i18n.js';
15
16
 
16
17
  export type BulkItemKind = 'plan';
17
18
 
@@ -62,11 +63,12 @@ export interface UseBulkPublishResult {
62
63
  }
63
64
 
64
65
  export function useBulkPublish(options: UseBulkPublishOptions): UseBulkPublishResult {
66
+ const msg = useSaMessages('planVersions');
65
67
  if (!options?.endpoints) {
66
68
  throw new Error(
67
- 'useBulkPublish: `endpoints` ist Pflicht (Mapping plan → URL-Builder). ' +
68
- 'Plattform hat keinen Default, weil Apps unterschiedliche ' +
69
- 'globalPrefix-Konventionen haben.',
69
+ 'useBulkPublish: `endpoints` is required (mapping plan → URL builder). ' +
70
+ 'The platform has no default because apps use different ' +
71
+ 'globalPrefix conventions.',
70
72
  );
71
73
  }
72
74
  const http = options.http ?? defaultHttpClient();
@@ -128,7 +130,7 @@ export function useBulkPublish(options: UseBulkPublishOptions): UseBulkPublishRe
128
130
  // Mark all items as failed with a clear reason.
129
131
  for (const item of items.value) {
130
132
  item.status = 'failed';
131
- item.error = 'changeNote ist Pflicht beim Publish.';
133
+ item.error = msg.value.bulkPublish.changeNoteRequired;
132
134
  }
133
135
  return;
134
136
  }
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { ref, watch, type Ref } from 'vue';
13
13
  import type { BundleRow, BundleVersionRow } from '@saasicat/types';
14
- import { defaultHttpClient, type HttpClient } from './types.js';
14
+ import { defaultHttpClient, type HttpClient } from '../client/types.js';
15
15
 
16
16
  export interface UseBundleVersionsMapOptions {
17
17
  adminEndpoint: string;
@@ -36,10 +36,10 @@ export function useBundleVersionsMap(
36
36
  options: UseBundleVersionsMapOptions,
37
37
  ): UseBundleVersionsMapResult {
38
38
  if (!options?.adminEndpoint) {
39
- throw new Error('useBundleVersionsMap: `adminEndpoint` ist Pflicht.');
39
+ throw new Error('useBundleVersionsMap: `adminEndpoint` is required.');
40
40
  }
41
41
  if (!options?.bundles) {
42
- throw new Error('useBundleVersionsMap: `bundles` ist Pflicht.');
42
+ throw new Error('useBundleVersionsMap: `bundles` is required.');
43
43
  }
44
44
 
45
45
  const http = options.http ?? defaultHttpClient();
@@ -58,7 +58,9 @@ export function useBundleVersionsMap(
58
58
  headers: { 'content-type': 'application/json', ...authHeaders() },
59
59
  });
60
60
  if (res.status >= 400) {
61
- throw new Error(`HTTP ${res.status} beim Laden der Versionen für Bundle '${bundleId}'`);
61
+ throw new Error(
62
+ `HTTP ${res.status} while loading the versions for bundle '${bundleId}'`,
63
+ );
62
64
  }
63
65
  return ((await res.json().catch(() => null)) as BundleVersionRow[] | null) ?? [];
64
66
  }
@@ -74,7 +76,7 @@ export function useBundleVersionsMap(
74
76
  return [b.id, versions] as const;
75
77
  } catch (err) {
76
78
  console.warn(
77
- `useBundleVersionsMap: Bundle '${b.bundleKey}' Versions-Load failed`,
79
+ `useBundleVersionsMap: loading versions for bundle '${b.bundleKey}' failed`,
78
80
  err,
79
81
  );
80
82
  return [b.id, []] as const;
@@ -16,7 +16,7 @@ import type {
16
16
  UpdateBundleData,
17
17
  UpdateBundleVersionDraftData,
18
18
  } from '@saasicat/types';
19
- import { defaultHttpClient, type HttpClient } from './types.js';
19
+ import { defaultHttpClient, type HttpClient } from '../client/types.js';
20
20
 
21
21
  export interface UseBundlesOptions {
22
22
  /**
@@ -58,13 +58,13 @@ export interface UseBundlesResult {
58
58
  export function useBundles(options: UseBundlesOptions): UseBundlesResult {
59
59
  if (!options?.adminEndpoint) {
60
60
  throw new Error(
61
- 'useBundles: `adminEndpoint` ist Pflicht (z. B. "/api/admin" oder ' +
62
- '"/api/v1/admin"). Plattform hat keinen Default, weil Apps ' +
63
- 'unterschiedliche globalPrefix-Konventionen haben.',
61
+ 'useBundles: `adminEndpoint` is required (e.g. "/api/admin" or ' +
62
+ '"/api/v1/admin"). The platform has no default, because apps use ' +
63
+ 'different globalPrefix conventions.',
64
64
  );
65
65
  }
66
66
  if (!options?.projectKey) {
67
- throw new Error('useBundles: `projectKey` ist Pflicht.');
67
+ throw new Error('useBundles: `projectKey` is required.');
68
68
  }
69
69
 
70
70
  const http = options.http ?? defaultHttpClient();
@@ -91,7 +91,7 @@ export function useBundles(options: UseBundlesOptions): UseBundlesResult {
91
91
  throw new BundlesApiError(
92
92
  res.status,
93
93
  body,
94
- `Bundles-API antwortete mit HTTP ${res.status}`,
94
+ `Bundles API responded with HTTP ${res.status}`,
95
95
  );
96
96
  }
97
97
  return body as T;
@@ -117,7 +117,7 @@ export function useBundles(options: UseBundlesOptions): UseBundlesResult {
117
117
  method: 'POST',
118
118
  body: JSON.stringify(data),
119
119
  });
120
- if (!created) throw new BundlesApiError(0, null, 'Create gab keinen Body zurück');
120
+ if (!created) throw new BundlesApiError(0, null, 'Create returned no body');
121
121
  bundles.value = [...bundles.value, created];
122
122
  return created;
123
123
  }
@@ -127,7 +127,7 @@ export function useBundles(options: UseBundlesOptions): UseBundlesResult {
127
127
  method: 'PATCH',
128
128
  body: JSON.stringify(data),
129
129
  });
130
- if (!updated) throw new BundlesApiError(0, null, 'Update gab keinen Body zurück');
130
+ if (!updated) throw new BundlesApiError(0, null, 'Update returned no body');
131
131
  bundles.value = bundles.value.map((b) => (b.id === bundleId ? updated : b));
132
132
  return updated;
133
133
  }
@@ -189,10 +189,10 @@ export interface UseBundleVersionsResult {
189
189
 
190
190
  export function useBundleVersions(options: UseBundleVersionsOptions): UseBundleVersionsResult {
191
191
  if (!options?.adminEndpoint) {
192
- throw new Error('useBundleVersions: `adminEndpoint` ist Pflicht.');
192
+ throw new Error('useBundleVersions: `adminEndpoint` is required.');
193
193
  }
194
194
  if (!options?.bundleId) {
195
- throw new Error('useBundleVersions: `bundleId` ist Pflicht.');
195
+ throw new Error('useBundleVersions: `bundleId` is required.');
196
196
  }
197
197
 
198
198
  const http = options.http ?? defaultHttpClient();
@@ -220,7 +220,7 @@ export function useBundleVersions(options: UseBundleVersionsOptions): UseBundleV
220
220
  throw new BundlesApiError(
221
221
  res.status,
222
222
  body,
223
- `BundleVersions-API antwortete mit HTTP ${res.status}`,
223
+ `BundleVersions API responded with HTTP ${res.status}`,
224
224
  );
225
225
  }
226
226
  return body as T;
@@ -246,7 +246,7 @@ export function useBundleVersions(options: UseBundleVersionsOptions): UseBundleV
246
246
  method: 'POST',
247
247
  body: JSON.stringify(data),
248
248
  });
249
- if (!result) throw new BundlesApiError(0, null, 'CreateDraft gab keinen Body zurück');
249
+ if (!result) throw new BundlesApiError(0, null, 'CreateDraft returned no body');
250
250
  versions.value = [...versions.value, result.bundleVersion];
251
251
  return result;
252
252
  }
@@ -259,7 +259,7 @@ export function useBundleVersions(options: UseBundleVersionsOptions): UseBundleV
259
259
  `${versionUrlBase}/${versionId}`,
260
260
  { method: 'PATCH', body: JSON.stringify(data) },
261
261
  );
262
- if (!result) throw new BundlesApiError(0, null, 'UpdateDraft gab keinen Body zurück');
262
+ if (!result) throw new BundlesApiError(0, null, 'UpdateDraft returned no body');
263
263
  versions.value = versions.value.map((v) => (v.id === versionId ? result.bundleVersion : v));
264
264
  return result;
265
265
  }
@@ -277,7 +277,7 @@ export function useBundleVersions(options: UseBundleVersionsOptions): UseBundleV
277
277
  `${versionUrlBase}/${versionId}/publish`,
278
278
  { method: 'POST', body: JSON.stringify(opts) },
279
279
  );
280
- if (!result) throw new BundlesApiError(0, null, 'Publish gab keinen Body zurück');
280
+ if (!result) throw new BundlesApiError(0, null, 'Publish returned no body');
281
281
  // Reload versions, because publishing may mark another version as
282
282
  // superseded — the local cache would otherwise be inconsistent.
283
283
  await load();