@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
@@ -4,8 +4,8 @@
4
4
  <div class="sa-setup-head">
5
5
  <div class="sa-setup-badge">{{ iconText }}</div>
6
6
  <div>
7
- <div class="sa-setup-title">Erst­einrichtung</div>
8
- <div class="sa-setup-sub">{{ brandName }} · ersten SuperAdmin anlegen</div>
7
+ <div class="sa-setup-title">{{ msg.setup.title }}</div>
8
+ <div class="sa-setup-sub">{{ subtitleText }}</div>
9
9
  </div>
10
10
  </div>
11
11
 
@@ -16,40 +16,44 @@
16
16
  <!-- Step 1: create SuperAdmin -->
17
17
  <q-form v-if="step === 'form'" @submit.prevent="submitCreate" class="sa-setup-form">
18
18
  <p class="sa-setup-hint">
19
- Es existiert noch kein SuperAdmin. Gib das vom Betreiber gesetzte
20
- <code>SETUP_TOKEN</code> ein und lege den ersten Account an.
19
+ {{ msg.setup.tokenHintBefore }}
20
+ <code>SETUP_TOKEN</code>
21
+ {{ msg.setup.tokenHintAfter }}
21
22
  </p>
22
23
  <q-input
23
24
  v-model="form.token"
24
- label="Setup-Token"
25
+ :label="msg.setup.tokenLabel"
25
26
  type="password"
26
27
  outlined
27
28
  dense
28
29
  autofocus
29
30
  :disable="loading"
30
31
  class="q-mb-sm"
31
- :rules="[(v: string) => v.length > 0 || 'Setup-Token ist Pflicht']"
32
+ :rules="[(v: string) => v.length > 0 || msg.setup.tokenRequired]"
32
33
  />
33
34
  <q-input
34
35
  v-model="form.email"
35
- label="E-Mail"
36
+ :label="msg.emailLabel"
36
37
  type="email"
37
38
  outlined
38
39
  dense
39
40
  :disable="loading"
40
41
  class="q-mb-sm"
41
- :rules="[(v: string) => /\S+@\S+\.\S+/.test(v) || 'Bitte gültige E-Mail']"
42
+ :rules="[(v: string) => /\S+@\S+\.\S+/.test(v) || msg.invalidEmail]"
42
43
  />
43
44
  <q-input
44
45
  v-model="form.password"
45
- label="Passwort (leer = generieren)"
46
+ :label="msg.setup.passwordLabel"
46
47
  :type="showPw ? 'text' : 'password'"
47
48
  outlined
48
49
  dense
49
50
  :disable="loading"
50
- hint="Mindestens 8 Zeichen, oder leer lassen für ein generiertes Passwort."
51
+ :hint="msg.setup.passwordHint"
51
52
  class="q-mb-sm"
52
- :rules="[(v: string) => v.length === 0 || v.length >= 8 || 'Mindestens 8 Zeichen']"
53
+ :rules="[
54
+ (v: string) =>
55
+ v.length === 0 || v.length >= 8 || msg.setup.passwordMinLength,
56
+ ]"
53
57
  >
54
58
  <template #append>
55
59
  <q-icon
@@ -63,7 +67,7 @@
63
67
  unelevated
64
68
  color="primary"
65
69
  icon="person_add"
66
- label="SuperAdmin anlegen"
70
+ :label="msg.setup.submit"
67
71
  :loading="loading"
68
72
  :disable="!canCreate"
69
73
  class="full-width q-mt-sm"
@@ -74,48 +78,48 @@
74
78
  <!-- Step 2: set up MFA -->
75
79
  <div v-else-if="step === 'mfa' && result" class="sa-setup-mfa">
76
80
  <p class="sa-setup-hint">
77
- Account <strong>{{ result.email }}</strong> angelegt. Richte jetzt die
78
- Zwei-Faktor-Authentisierung (TOTP) ein.
81
+ {{ msg.setup.mfaIntroBefore }} <strong>{{ result.email }}</strong>
82
+ {{ msg.setup.mfaIntroAfter }}
79
83
  </p>
80
84
 
81
85
  <div v-if="result.generatedPassword" class="sa-setup-secret">
82
- <div class="sa-setup-secret__label">Generiertes Passwort (einmalig sichern)</div>
86
+ <div class="sa-setup-secret__label">
87
+ {{ msg.setup.generatedPasswordLabel }}
88
+ </div>
83
89
  <code class="sa-setup-secret__value">{{ result.generatedPassword }}</code>
84
90
  </div>
85
91
 
86
92
  <div v-if="result.qrDataUrl" class="sa-setup-qr">
87
- <img :src="result.qrDataUrl" alt="MFA-QR-Code" width="200" height="200" />
88
- <div class="sa-setup-qr__hint">Mit der Authenticator-App scannen</div>
93
+ <img :src="result.qrDataUrl" :alt="msg.setup.qrAlt" width="200" height="200" />
94
+ <div class="sa-setup-qr__hint">{{ msg.setup.qrHint }}</div>
89
95
  </div>
90
96
 
91
97
  <div class="sa-setup-secret">
92
- <div class="sa-setup-secret__label">
93
- In Authenticator-App als „Schlüssel manuell eingeben"
94
- </div>
98
+ <div class="sa-setup-secret__label">{{ msg.setup.manualSecretLabel }}</div>
95
99
  <code class="sa-setup-secret__value">{{ result.secret }}</code>
96
100
  </div>
97
101
  <details class="sa-setup-uri">
98
- <summary>otpauth-URI (für QR-Generator)</summary>
102
+ <summary>{{ msg.setup.otpauthSummary }}</summary>
99
103
  <code class="sa-setup-uri__value">{{ result.otpauthUri }}</code>
100
104
  </details>
101
105
 
102
106
  <q-form @submit.prevent="submitConfirm" class="sa-setup-form q-mt-md">
103
107
  <q-input
104
108
  v-model="mfaCode"
105
- label="6-stelliger Code aus der App"
109
+ :label="msg.setup.codeLabel"
106
110
  outlined
107
111
  dense
108
112
  autofocus
109
113
  inputmode="numeric"
110
114
  maxlength="6"
111
115
  :disable="loading"
112
- :rules="[(v: string) => /^\d{6}$/.test(v) || '6-stelliger Code']"
116
+ :rules="[(v: string) => /^\d{6}$/.test(v) || msg.setup.codeInvalid]"
113
117
  />
114
118
  <q-btn
115
119
  unelevated
116
120
  color="primary"
117
121
  icon="verified_user"
118
- label="MFA bestätigen"
122
+ :label="msg.setup.confirmMfa"
119
123
  :loading="loading"
120
124
  :disable="!/^\d{6}$/.test(mfaCode) || loading"
121
125
  class="full-width q-mt-sm"
@@ -123,21 +127,19 @@
123
127
  />
124
128
  </q-form>
125
129
  <button class="sa-setup-skip" type="button" @click="step = 'done'">
126
- Überspringen Code beim ersten Login eingeben
130
+ {{ msg.setup.skip }}
127
131
  </button>
128
132
  </div>
129
133
 
130
134
  <!-- Step 3: done -->
131
135
  <div v-else-if="step === 'done'" class="sa-setup-done">
132
136
  <q-icon name="check_circle" color="positive" size="48px" />
133
- <p class="sa-setup-hint">
134
- Einrichtung abgeschlossen. Melde dich jetzt mit dem neuen SuperAdmin an.
135
- </p>
137
+ <p class="sa-setup-hint">{{ msg.setup.doneHint }}</p>
136
138
  <q-btn
137
139
  unelevated
138
140
  color="primary"
139
141
  icon="login"
140
- label="Zum Login"
142
+ :label="msg.setup.toLogin"
141
143
  class="full-width"
142
144
  @click="emit('done')"
143
145
  />
@@ -148,18 +150,16 @@
148
150
 
149
151
  <script setup lang="ts">
150
152
  import { computed, reactive, ref } from 'vue';
151
- import {
152
- SETUP_ERROR_CODES,
153
- type SetupConfirmMfaResponse,
154
- type SetupResult,
155
- } from '@saasicat/types';
153
+ import { SETUP_ERROR_CODES, type SetupConfirmMfaResponse, type SetupResult } from '@saasicat/types';
156
154
 
157
155
  import {
158
156
  useSuperAdminBrand,
159
157
  useSuperAdminEndpoints,
160
158
  useSuperAdminHttp,
161
- } from '../use-super-admin-context.js';
162
- import { HttpJsonError, postJson as httpPostJson } from '../http-json.js';
159
+ } from '../vue/use-super-admin-context.js';
160
+ import { HttpJsonError, postJson as httpPostJson } from '../client/http-json.js';
161
+ import { formatMessage } from '../client/i18n/format.js';
162
+ import { useSaMessages } from '../vue/use-super-admin-i18n.js';
163
163
 
164
164
  interface Props {
165
165
  /** Display name + badge abbreviation (override the app branding, e.g. from PublicBoot). */
@@ -172,12 +172,16 @@ const props = defineProps<Props>();
172
172
  // shown again — the freshly created SuperAdmin then signs in.
173
173
  const emit = defineEmits<{ done: [] }>();
174
174
 
175
+ const msg = useSaMessages('shell');
175
176
  const brand = useSuperAdminBrand();
176
177
  const endpoints = useSuperAdminEndpoints();
177
178
  const http = useSuperAdminHttp();
178
179
 
179
180
  const brandName = computed(() => props.displayName ?? brand.name);
180
181
  const iconText = computed(() => props.icon ?? brand.logoText);
182
+ const subtitleText = computed(() =>
183
+ formatMessage(msg.value.setup.subtitle, { brand: brandName.value }),
184
+ );
181
185
 
182
186
  const step = ref<'form' | 'mfa' | 'done'>('form');
183
187
  const form = reactive({ token: '', email: '', password: '' });
@@ -195,15 +199,13 @@ const canCreate = computed(
195
199
  !loading.value,
196
200
  );
197
201
 
198
- const ERROR_BY_CODE: Record<string, string> = {
199
- [SETUP_ERROR_CODES.SETUP_DISABLED]: 'Setup ist serverseitig deaktiviert (SETUP_TOKEN nicht gesetzt).',
200
- [SETUP_ERROR_CODES.INVALID_SETUP_TOKEN]: 'Setup-Token ungültig.',
201
- [SETUP_ERROR_CODES.SETUP_ALREADY_DONE]:
202
- 'Es existiert bereits ein SuperAdmin — Setup ist abgeschlossen.',
203
- [SETUP_ERROR_CODES.INVALID_EMAIL]: 'Ungültige E-Mail-Adresse.',
204
- [SETUP_ERROR_CODES.EMAIL_EXISTS]:
205
- 'Diese E-Mail ist bereits vergeben. Wähle eine andere oder hebe den bestehenden User per CLI/DB zum SUPER_ADMIN an.',
206
- };
202
+ const errorByCode = computed<Record<string, string>>(() => ({
203
+ [SETUP_ERROR_CODES.SETUP_DISABLED]: msg.value.setup.errorSetupDisabled,
204
+ [SETUP_ERROR_CODES.INVALID_SETUP_TOKEN]: msg.value.setup.errorInvalidToken,
205
+ [SETUP_ERROR_CODES.SETUP_ALREADY_DONE]: msg.value.setup.errorAlreadyDone,
206
+ [SETUP_ERROR_CODES.INVALID_EMAIL]: msg.value.setup.errorInvalidEmail,
207
+ [SETUP_ERROR_CODES.EMAIL_EXISTS]: msg.value.setup.errorEmailExists,
208
+ }));
207
209
 
208
210
  // Goes through the injected HttpClient (consumer's auth/baseURL apply);
209
211
  // maps the error code from the body to a readable message.
@@ -213,7 +215,8 @@ async function postJson<T>(path: string, body: unknown): Promise<T> {
213
215
  } catch (err) {
214
216
  if (err instanceof HttpJsonError) {
215
217
  throw new Error(
216
- (err.code && ERROR_BY_CODE[err.code]) || `Fehler (HTTP ${err.status}).`,
218
+ (err.code && errorByCode.value[err.code]) ||
219
+ formatMessage(msg.value.setup.errorHttp, { status: err.status }),
217
220
  );
218
221
  }
219
222
  throw err;
@@ -232,7 +235,7 @@ async function submitCreate(): Promise<void> {
232
235
  });
233
236
  step.value = 'mfa';
234
237
  } catch (err) {
235
- errorMessage.value = err instanceof Error ? err.message : 'Setup fehlgeschlagen.';
238
+ errorMessage.value = err instanceof Error ? err.message : msg.value.setup.errorFailed;
236
239
  } finally {
237
240
  loading.value = false;
238
241
  }
@@ -251,10 +254,11 @@ async function submitConfirm(): Promise<void> {
251
254
  if (res.ok) {
252
255
  step.value = 'done';
253
256
  } else {
254
- errorMessage.value = 'Code ungültig — bitte erneut versuchen.';
257
+ errorMessage.value = msg.value.setup.errorCodeInvalid;
255
258
  }
256
259
  } catch (err) {
257
- errorMessage.value = err instanceof Error ? err.message : 'Bestätigung fehlgeschlagen.';
260
+ errorMessage.value =
261
+ err instanceof Error ? err.message : msg.value.setup.errorConfirmFailed;
258
262
  } finally {
259
263
  loading.value = false;
260
264
  }
@@ -6,13 +6,13 @@
6
6
  flat
7
7
  dense
8
8
  icon="arrow_back"
9
- :label="backLabel"
9
+ :label="labels.back"
10
10
  :to="backRoute"
11
11
  class="sa-tenant-detail__back"
12
12
  />
13
- <h1 class="sa-page-head__title">{{ data?.name ?? titleLabel }}</h1>
13
+ <h1 class="sa-page-head__title">{{ data?.name ?? labels.title }}</h1>
14
14
  <p v-if="data" class="sa-page-head__sub">
15
- {{ slugLabel }}: <code>{{ data.slug }}</code>
15
+ {{ labels.slug }}: <code>{{ data.slug }}</code>
16
16
  </p>
17
17
  </div>
18
18
  <slot name="header-actions" :data="data" :reload="load" />
@@ -20,7 +20,7 @@
20
20
 
21
21
  <div class="sa-tenant-detail__body">
22
22
  <div v-if="loading" class="sa-tenant-detail__state">
23
- <q-spinner size="32px" /> wird geladen…
23
+ <q-spinner size="32px" /> {{ common.loading }}
24
24
  </div>
25
25
 
26
26
  <template v-else-if="data">
@@ -28,7 +28,7 @@
28
28
  <div class="sa-card q-mb-md">
29
29
  <header class="sa-tenant-detail__card-head">
30
30
  <div>
31
- <h2 class="sa-card__title">{{ stammdatenLabel }}</h2>
31
+ <h2 class="sa-card__title">{{ labels.masterData }}</h2>
32
32
  <p v-if="stammdatenSub" class="sa-tenant-detail__card-sub">
33
33
  {{ stammdatenSub }}
34
34
  </p>
@@ -49,24 +49,24 @@
49
49
  </header>
50
50
  <slot name="stammdaten" :data="data">
51
51
  <div class="sa-tenant-detail__grid">
52
- <KvBlock :label="planLabel" :value="data.subscription?.plan ?? '—'" />
52
+ <KvBlock :label="labels.plan" :value="data.subscription?.plan ?? '—'" />
53
53
  <KvBlock
54
- :label="statusLabel"
54
+ :label="labels.status"
55
55
  :value="data.subscription?.status ?? '—'"
56
56
  />
57
57
  <KvBlock
58
- :label="pilotLabel"
59
- :value="data.subscription?.isPilot ? 'ja' : 'nein'"
58
+ :label="labels.pilot"
59
+ :value="data.subscription?.isPilot ? common.yes : common.no"
60
60
  />
61
61
  <KvBlock
62
- :label="trialEndLabel"
62
+ :label="labels.trialEnd"
63
63
  :value="formatDate(data.subscription?.trialEndsAt)"
64
64
  />
65
65
  <KvBlock
66
- :label="pilotEndLabel"
66
+ :label="labels.pilotEnd"
67
67
  :value="formatDate(data.subscription?.pilotEndsAt)"
68
68
  />
69
- <KvBlock v-if="data.vatId" :label="vatIdLabel" :value="data.vatId" />
69
+ <KvBlock v-if="data.vatId" :label="labels.vatId" :value="data.vatId" />
70
70
  <slot name="extra-stammdaten" :data="data" />
71
71
  </div>
72
72
  </slot>
@@ -74,7 +74,7 @@
74
74
 
75
75
  <!-- Usage -->
76
76
  <div v-if="verbrauchFields.length > 0" class="sa-card q-mb-md">
77
- <h3 class="sa-card__title">{{ verbrauchLabel }}</h3>
77
+ <h3 class="sa-card__title">{{ labels.usage }}</h3>
78
78
  <div class="sa-tenant-detail__grid">
79
79
  <KvBlock
80
80
  v-for="(field, i) in verbrauchFields"
@@ -87,11 +87,11 @@
87
87
 
88
88
  <!-- Users -->
89
89
  <div v-if="showUsers && data.users" class="sa-card q-mb-md">
90
- <h3 class="sa-card__title">{{ usersLabel }}</h3>
90
+ <h3 class="sa-card__title">{{ labels.users }}</h3>
91
91
  <q-table
92
92
  flat
93
93
  :rows="data.users"
94
- :columns="userColumns ?? DEFAULT_USER_COLUMNS"
94
+ :columns="userColumns ?? defaultUserColumns"
95
95
  row-key="id"
96
96
  :pagination="{ rowsPerPage: 0 }"
97
97
  hide-pagination
@@ -125,12 +125,14 @@
125
125
  import { computed, onMounted, ref, toRef } from 'vue';
126
126
  import type { RouteLocationRaw } from 'vue-router';
127
127
  import type { QTableColumn } from 'quasar';
128
- import { useQuasar } from 'quasar';
128
+ import { useSuperAdminNotify } from '../quasar/notify.js';
129
129
  import type { AdminManifest, TenantActionDef, TenantDto } from '@saasicat/types';
130
+ import { formatMessage } from '../client/i18n/format.js';
130
131
  import KvBlock from '../components/KvBlock.vue';
131
132
  import MfaPromptDialog from '../components/MfaPromptDialog.vue';
132
133
  import TenantActionConfirmDialog from '../components/TenantActionConfirmDialog.vue';
133
- import { useTenantActionFlow } from '../use-tenant-action-flow.js';
134
+ import { useSaMessages } from '../vue/use-super-admin-i18n.js';
135
+ import { useTenantActionFlow } from '../vue/use-tenant-action-flow.js';
134
136
 
135
137
  export interface TenantDetailData {
136
138
  id: string;
@@ -185,22 +187,30 @@ const props = withDefaults(
185
187
  {
186
188
  verbrauchFields: () => [],
187
189
  showUsers: true,
188
- backLabel: 'Mandanten-Liste',
189
- titleLabel: 'Mandant',
190
- slugLabel: 'Slug',
191
- stammdatenLabel: 'Stammdaten',
192
- planLabel: 'Plan',
193
- statusLabel: 'Status',
194
- pilotLabel: 'Pilot',
195
- trialEndLabel: 'Trial-Ende',
196
- pilotEndLabel: 'Pilot-Ende',
197
- vatIdLabel: 'USt-IdNr.',
198
- verbrauchLabel: 'Verbrauch',
199
- usersLabel: 'User',
200
190
  },
201
191
  );
202
192
 
203
- const q = useQuasar();
193
+ const msg = useSaMessages('tenants');
194
+ const common = useSaMessages('common');
195
+
196
+ // Every label stays overridable via props — only the German literals moved
197
+ // into the catalog.
198
+ const labels = computed(() => ({
199
+ back: props.backLabel ?? msg.value.detail.backToList,
200
+ title: props.titleLabel ?? msg.value.tenant,
201
+ slug: props.slugLabel ?? msg.value.detail.slug,
202
+ masterData: props.stammdatenLabel ?? msg.value.detail.masterData,
203
+ plan: props.planLabel ?? msg.value.plan,
204
+ status: props.statusLabel ?? common.value.status,
205
+ pilot: props.pilotLabel ?? msg.value.detail.pilot,
206
+ trialEnd: props.trialEndLabel ?? msg.value.detail.trialEnd,
207
+ pilotEnd: props.pilotEndLabel ?? msg.value.detail.pilotEnd,
208
+ vatId: props.vatIdLabel ?? msg.value.detail.vatId,
209
+ usage: props.verbrauchLabel ?? msg.value.detail.usage,
210
+ users: props.usersLabel ?? msg.value.detail.users,
211
+ }));
212
+
213
+ const notify = useSuperAdminNotify();
204
214
  const data = ref<TenantDetailData | null>(null);
205
215
  const loading = ref(false);
206
216
 
@@ -250,7 +260,10 @@ function showMfaDialog(def: TenantActionDef, ctx: { row: TenantDto }): Promise<s
250
260
  return new Promise((resolve) => {
251
261
  mfaState.value = {
252
262
  show: true,
253
- description: `${def.label} — Tenant „${ctx.row.name}". TOTP-Code aus Authenticator eingeben.`,
263
+ description: formatMessage(msg.value.actions.mfaDescription, {
264
+ action: def.label,
265
+ tenant: ctx.row.name,
266
+ }),
254
267
  error: '',
255
268
  };
256
269
  pendingMfaResolve = (code) => {
@@ -317,7 +330,7 @@ const tenantRow = computed<TenantDto | null>(() => {
317
330
  const flow = useTenantActionFlow<TenantDto>(manifestRef, {
318
331
  confirm: showConfirmDialog,
319
332
  mfa: showMfaDialog,
320
- notify: (kind, message) => q.notify({ type: kind, message, position: 'top' }),
333
+ notify,
321
334
  onSuccess: () => void load(),
322
335
  visibleForRow: (def, row) => {
323
336
  if (def.actionKey === 'tenants.suspend') return row.isActive;
@@ -347,34 +360,35 @@ function toneColor(actionKey: string): string {
347
360
  return 'primary';
348
361
  }
349
362
 
350
- const DEFAULT_USER_COLUMNS: QTableColumn[] = [
351
- { name: 'email', label: 'E-Mail', field: 'email', align: 'left' },
363
+ const defaultUserColumns = computed<QTableColumn[]>(() => [
364
+ { name: 'email', label: msg.value.detail.userEmail, field: 'email', align: 'left' },
352
365
  {
353
366
  name: 'name',
354
- label: 'Name',
367
+ label: common.value.name,
355
368
  field: (r: unknown) => {
356
369
  const row = r as Record<string, unknown>;
357
370
  return `${row.firstName ?? ''} ${row.lastName ?? ''}`.trim();
358
371
  },
359
372
  align: 'left',
360
373
  },
361
- { name: 'role', label: 'Rolle', field: 'role', align: 'left' },
374
+ { name: 'role', label: msg.value.detail.userRole, field: 'role', align: 'left' },
362
375
  {
363
376
  name: 'status',
364
- label: 'Status',
365
- field: (r: unknown) => ((r as Record<string, unknown>).isActive ? 'aktiv' : 'deaktiviert'),
377
+ label: common.value.status,
378
+ field: (r: unknown) =>
379
+ (r as Record<string, unknown>).isActive ? common.value.active : common.value.inactive,
366
380
  align: 'left',
367
381
  },
368
382
  {
369
383
  name: 'lastLogin',
370
- label: 'Letzter Login',
384
+ label: msg.value.detail.userLastLogin,
371
385
  field: (r: unknown) => {
372
386
  const v = (r as Record<string, unknown>).lastLoginAt;
373
387
  return v ? String(v).slice(0, 10) : '—';
374
388
  },
375
389
  align: 'left',
376
390
  },
377
- ];
391
+ ]);
378
392
  </script>
379
393
 
380
394
  <style scoped>