@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
@@ -2,7 +2,7 @@
2
2
  <div class="sa-promo-codes">
3
3
  <header class="sa-page-head">
4
4
  <div>
5
- <h1 class="sa-page-head__title">Promo-Codes</h1>
5
+ <h1 class="sa-page-head__title">{{ msg.title }}</h1>
6
6
  <p v-if="subtitle" class="sa-page-head__sub">{{ subtitle }}</p>
7
7
  </div>
8
8
  <div class="sa-page-head__actions">
@@ -12,7 +12,7 @@
12
12
  unelevated
13
13
  color="primary"
14
14
  icon="add"
15
- :label="createLabel"
15
+ :label="resolvedCreateLabel"
16
16
  @click="showCreate = true"
17
17
  />
18
18
  </slot>
@@ -41,7 +41,7 @@
41
41
  v-model="filter.search"
42
42
  outlined
43
43
  dense
44
- label="Suche (Code)"
44
+ :label="msg.list.searchLabel"
45
45
  clearable
46
46
  @update:model-value="reload"
47
47
  />
@@ -52,7 +52,7 @@
52
52
  emit-value
53
53
  map-options
54
54
  clearable
55
- label="Status"
55
+ :label="common.status"
56
56
  :options="statusOptions"
57
57
  @update:model-value="reload"
58
58
  />
@@ -138,6 +138,9 @@ export function computePlanColors(
138
138
  <script setup lang="ts">
139
139
  import { computed, onMounted, reactive, ref } from 'vue';
140
140
  import { useQuasar } from 'quasar';
141
+ import { formatMessage } from '../client/i18n/format.js';
142
+ import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
143
+ import { useSuperAdminNotify } from '../quasar/notify.js';
141
144
  import PromoCodeCreateDialog from '../components/dialogs/PromoCodeCreateDialog.vue';
142
145
  import PromoCodeEditDialog, {
143
146
  type PromoCodeEditRow,
@@ -209,12 +212,17 @@ const props = withDefaults(
209
212
  }>(),
210
213
  {
211
214
  statusOptions: () => ['ACTIVE', 'PAUSED', 'EXHAUSTED', 'EXPIRED'],
212
- createLabel: 'Promo-Code anlegen',
213
215
  planOptions: () => [],
214
216
  },
215
217
  );
216
218
 
219
+ const msg = useSaMessages('promos');
220
+ const common = useSaMessages('common');
221
+ const { intlLocale } = useSuperAdminI18n();
222
+ const resolvedCreateLabel = computed(() => props.createLabel ?? msg.value.createAction);
223
+
217
224
  const q = useQuasar();
225
+ const notify = useSuperAdminNotify();
218
226
  const rows = ref<PromoRow[]>([]);
219
227
  const loading = ref(false);
220
228
  const filter = reactive({ search: '', status: null as string | null });
@@ -265,23 +273,23 @@ const statTiles = computed<
265
273
  if (c) counts[c]++;
266
274
  }
267
275
  return [
268
- { id: 'all', label: 'Alle', count: rows.value.length },
276
+ { id: 'all', label: common.value.all, count: rows.value.length },
269
277
  {
270
278
  id: 'active',
271
- label: 'Aktiv',
279
+ label: common.value.active,
272
280
  count: counts.active,
273
281
  tone: 'positive',
274
- hint: 'einlösbar',
282
+ hint: msg.value.list.hintRedeemable,
275
283
  },
276
284
  {
277
285
  id: 'scheduled',
278
- label: 'Geplant',
286
+ label: msg.value.list.tileScheduled,
279
287
  count: counts.scheduled,
280
288
  tone: 'info',
281
- hint: 'startet künftig',
289
+ hint: msg.value.list.hintStartsLater,
282
290
  },
283
- { id: 'paused', label: 'Pausiert', count: counts.paused, tone: 'warn' },
284
- { id: 'expired', label: 'Abgelaufen', count: counts.expired, tone: 'muted' },
291
+ { id: 'paused', label: msg.value.list.tilePaused, count: counts.paused, tone: 'warn' },
292
+ { id: 'expired', label: common.value.expired, count: counts.expired, tone: 'muted' },
285
293
  ];
286
294
  });
287
295
 
@@ -303,36 +311,41 @@ function onStatusTileClick(id: StatusFilter): void {
303
311
  }
304
312
  }
305
313
 
306
- const baseColumns = [
314
+ const baseColumns = computed(() => [
307
315
  {
308
316
  name: 'code',
309
- label: 'Code',
317
+ label: msg.value.list.columnCode,
310
318
  field: 'code',
311
319
  align: 'left' as const,
312
320
  sortable: true,
313
321
  },
314
- { name: 'valueType', label: 'Typ', field: 'valueType', align: 'left' as const },
315
- { name: 'value', label: 'Wert', field: 'value', align: 'right' as const },
316
- { name: 'status', label: 'Status', field: 'status', align: 'left' as const },
322
+ {
323
+ name: 'valueType',
324
+ label: common.value.type,
325
+ field: 'valueType',
326
+ align: 'left' as const,
327
+ },
328
+ { name: 'value', label: msg.value.list.columnValue, field: 'value', align: 'right' as const },
329
+ { name: 'status', label: common.value.status, field: 'status', align: 'left' as const },
317
330
  {
318
331
  name: 'redemptions',
319
- label: 'Einlösungen',
332
+ label: msg.value.list.columnRedemptions,
320
333
  field: (r: PromoRow) => `${r.redemptionsCount} / ${r.maxRedemptions ?? '∞'}`,
321
334
  align: 'right' as const,
322
335
  },
323
336
  {
324
337
  name: 'campaign',
325
- label: 'Kampagne',
338
+ label: msg.value.list.columnCampaign,
326
339
  field: (r: PromoRow) => r.campaignTag ?? '—',
327
340
  align: 'left' as const,
328
341
  },
329
342
  {
330
343
  name: 'validUntil',
331
- label: 'Gültig bis',
344
+ label: msg.value.list.columnValidUntil,
332
345
  field: (r: PromoRow) => formatDate(r.validUntil) ?? '—',
333
346
  align: 'left' as const,
334
347
  },
335
- ];
348
+ ]);
336
349
 
337
350
  // Built-in default actions — APPENDED to the consumer actions, not replacing them.
338
351
  const bakedActions = computed<PromoRowAction[]>(() => {
@@ -340,7 +353,7 @@ const bakedActions = computed<PromoRowAction[]>(() => {
340
353
  if (props.enableEdit && props.submitEdit) {
341
354
  out.push({
342
355
  id: 'edit',
343
- label: 'Bearbeiten',
356
+ label: common.value.edit,
344
357
  icon: 'edit',
345
358
  color: 'primary',
346
359
  handler: (row) => openEdit(row),
@@ -349,7 +362,7 @@ const bakedActions = computed<PromoRowAction[]>(() => {
349
362
  if (props.enableStatusToggle && props.submitEdit) {
350
363
  out.push({
351
364
  id: 'pause',
352
- label: 'Pausieren',
365
+ label: msg.value.list.actionPause,
353
366
  icon: 'pause',
354
367
  color: 'warning',
355
368
  condition: (row) => row.status === 'ACTIVE',
@@ -357,7 +370,7 @@ const bakedActions = computed<PromoRowAction[]>(() => {
357
370
  });
358
371
  out.push({
359
372
  id: 'activate',
360
- label: 'Aktivieren',
373
+ label: msg.value.list.actionActivate,
361
374
  icon: 'play_arrow',
362
375
  color: 'primary',
363
376
  condition: (row) => row.status === 'PAUSED',
@@ -367,7 +380,7 @@ const bakedActions = computed<PromoRowAction[]>(() => {
367
380
  if (props.enableDelete && props.submitDelete) {
368
381
  out.push({
369
382
  id: 'delete',
370
- label: 'Löschen',
383
+ label: common.value.delete,
371
384
  icon: 'delete',
372
385
  color: 'negative',
373
386
  handler: (row) => onDeleteClick(row),
@@ -382,7 +395,7 @@ const mergedActions = computed<readonly PromoRowAction[]>(() => [
382
395
  ]);
383
396
 
384
397
  const effectiveColumns = computed(() => {
385
- const cols = [...baseColumns];
398
+ const cols = [...baseColumns.value];
386
399
  if (mergedActions.value.length > 0) {
387
400
  cols.push({
388
401
  name: 'actions',
@@ -490,14 +503,10 @@ async function onPatch(row: PromoRow, data: PromoCodeUpdatePayload): Promise<voi
490
503
  if (!props.submitEdit) return;
491
504
  try {
492
505
  await props.submitEdit(row.id, data);
493
- q.notify({
494
- type: 'positive',
495
- message: `${row.code} → ${data.status}`,
496
- position: 'top',
497
- });
506
+ notify('positive', `${row.code} → ${data.status}`);
498
507
  await reload();
499
508
  } catch (err) {
500
- q.notify({ type: 'negative', message: errMsg(err), position: 'top' });
509
+ notify('negative', errMsg(err));
501
510
  }
502
511
  }
503
512
 
@@ -505,40 +514,28 @@ function onDeleteClick(row: PromoRow): void {
505
514
  if (!props.submitDelete) return;
506
515
  const submit = props.submitDelete;
507
516
  q.dialog({
508
- title: `Promo-Code "${row.code}" löschen`,
509
- message: 'Soft-Delete via Status=EXPIRED. Bestehende Redemptions bleiben.',
510
- cancel: 'Abbrechen',
511
- ok: { label: 'Löschen', color: 'negative' },
517
+ title: formatMessage(msg.value.list.deleteTitle, { code: row.code }),
518
+ message: msg.value.list.deleteMessage,
519
+ cancel: common.value.cancel,
520
+ ok: { label: common.value.delete, color: 'negative' },
512
521
  }).onOk(async () => {
513
522
  try {
514
523
  await submit(row.id);
515
- q.notify({
516
- type: 'positive',
517
- message: `${row.code} gelöscht.`,
518
- position: 'top',
519
- });
524
+ notify('positive', formatMessage(msg.value.list.deletedNotice, { code: row.code }));
520
525
  await reload();
521
526
  } catch (err) {
522
- q.notify({ type: 'negative', message: errMsg(err), position: 'top' });
527
+ notify('negative', errMsg(err));
523
528
  }
524
529
  });
525
530
  }
526
531
 
527
532
  function onCreated(): void {
528
- q.notify({
529
- type: 'positive',
530
- message: 'Promo-Code angelegt.',
531
- position: 'top',
532
- });
533
+ notify('positive', msg.value.list.createdNotice);
533
534
  void reload();
534
535
  }
535
536
 
536
537
  function onUpdated(): void {
537
- q.notify({
538
- type: 'positive',
539
- message: 'Promo-Code aktualisiert.',
540
- position: 'top',
541
- });
538
+ notify('positive', msg.value.list.updatedNotice);
542
539
  void reload();
543
540
  }
544
541
 
@@ -546,14 +543,14 @@ function errMsg(err: unknown): string {
546
543
  return (
547
544
  (err as { response?: { data?: { message?: string } } })?.response?.data?.message ??
548
545
  (err as Error)?.message ??
549
- 'Aktion fehlgeschlagen'
546
+ msg.value.list.actionFailed
550
547
  );
551
548
  }
552
549
 
553
550
  function formatDate(iso: string | Date | null | undefined): string | null {
554
551
  if (!iso) return null;
555
552
  try {
556
- return new Date(iso).toLocaleDateString('de-DE', {
553
+ return new Date(iso).toLocaleDateString(intlLocale.value, {
557
554
  day: '2-digit',
558
555
  month: 'short',
559
556
  year: 'numeric',
@@ -2,7 +2,7 @@
2
2
  <div class="sa-subscriptions">
3
3
  <header class="sa-subscriptions__head">
4
4
  <div>
5
- <h1 class="sa-subscriptions__title">Abonnements</h1>
5
+ <h1 class="sa-subscriptions__title">{{ msg.subscriptions.title }}</h1>
6
6
  <p v-if="subtitle" class="sa-subscriptions__sub">{{ subtitle }}</p>
7
7
  </div>
8
8
  <slot name="head-actions" />
@@ -26,6 +26,7 @@
26
26
 
27
27
  <script setup lang="ts">
28
28
  import { computed, onMounted, ref } from 'vue';
29
+ import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
29
30
 
30
31
  // Platform standard page: subscriptions (minimal). Apps pass
31
32
  // `loadSubscriptions` + optional `columns` through — default columns show
@@ -60,39 +61,43 @@ const props = withDefaults(
60
61
  {},
61
62
  );
62
63
 
64
+ const msg = useSaMessages('tenants');
65
+ const common = useSaMessages('common');
66
+ const { intlLocale } = useSuperAdminI18n();
67
+
63
68
  const rows = ref<SubscriptionRow[]>([]);
64
69
  const loading = ref(false);
65
70
 
66
- const defaultColumns: Column[] = [
71
+ const defaultColumns = computed<Column[]>(() => [
67
72
  {
68
73
  name: 'tenant',
69
- label: 'Mandant',
74
+ label: msg.value.tenant,
70
75
  field: (r) => r.tenant?.name ?? r.tenant?.slug ?? r.tenantSlug ?? '—',
71
76
  align: 'left',
72
77
  sortable: true,
73
78
  },
74
79
  {
75
80
  name: 'plan',
76
- label: 'Plan',
81
+ label: msg.value.plan,
77
82
  field: (r) => r.plan ?? r.planId ?? '—',
78
83
  align: 'left',
79
84
  },
80
85
  {
81
86
  name: 'status',
82
- label: 'Status',
87
+ label: common.value.status,
83
88
  field: (r) => r.status ?? '—',
84
89
  align: 'left',
85
90
  },
86
91
  {
87
92
  name: 'periodEndsAt',
88
- label: 'Endet',
93
+ label: msg.value.subscriptions.columnEndsAt,
89
94
  field: (r) => formatDate(r.periodEndsAt) ?? '∞',
90
95
  align: 'left',
91
96
  },
92
- ];
97
+ ]);
93
98
 
94
99
  const effectiveColumns = computed<Column[]>(() =>
95
- props.columns ? [...props.columns] : defaultColumns,
100
+ props.columns ? [...props.columns] : defaultColumns.value,
96
101
  );
97
102
 
98
103
  async function reload() {
@@ -112,7 +117,7 @@ onMounted(reload);
112
117
  function formatDate(iso: string | null | undefined): string | null {
113
118
  if (!iso) return null;
114
119
  try {
115
- return new Date(iso).toLocaleDateString('de-DE', {
120
+ return new Date(iso).toLocaleDateString(intlLocale.value, {
116
121
  day: '2-digit',
117
122
  month: 'short',
118
123
  year: 'numeric',
@@ -20,30 +20,32 @@
20
20
  </div>
21
21
  </div>
22
22
 
23
- <h1 class="sa-login-title">Anmelden</h1>
23
+ <h1 class="sa-login-title">{{ msg.login.signIn }}</h1>
24
24
  <p v-if="subtitle" class="sa-login-subtitle">{{ subtitle }}</p>
25
25
 
26
26
  <q-form @submit.prevent="handleSubmit" class="sa-login-form">
27
27
  <q-input
28
28
  v-model="form.email"
29
- label="E-Mail"
29
+ :label="msg.emailLabel"
30
30
  type="email"
31
+ autocomplete="username"
31
32
  outlined
32
33
  dense
33
34
  autofocus
34
35
  :disable="loading"
35
36
  class="q-mb-sm"
36
- :rules="[(v: string) => /\S+@\S+\.\S+/.test(v) || 'Bitte gültige E-Mail']"
37
+ :rules="[(v: string) => /\S+@\S+\.\S+/.test(v) || msg.invalidEmail]"
37
38
  />
38
39
  <q-input
39
40
  v-model="form.password"
40
- label="Passwort"
41
+ :label="msg.passwordLabel"
41
42
  :type="showPw ? 'text' : 'password'"
43
+ autocomplete="current-password"
42
44
  outlined
43
45
  dense
44
46
  :disable="loading"
45
47
  class="q-mb-sm"
46
- :rules="[(v: string) => v.length > 0 || 'Passwort ist Pflicht']"
48
+ :rules="[(v: string) => v.length > 0 || msg.login.passwordRequired]"
47
49
  >
48
50
  <template #append>
49
51
  <q-icon
@@ -62,7 +64,7 @@
62
64
  unelevated
63
65
  color="primary"
64
66
  icon="login"
65
- label="Anmelden"
67
+ :label="msg.login.signIn"
66
68
  :loading="loading"
67
69
  :disable="!canSubmit"
68
70
  class="full-width q-mt-sm"
@@ -71,7 +73,8 @@
71
73
  </q-form>
72
74
 
73
75
  <div v-if="devHint" class="sa-login-hint">
74
- Test-Account: <code>{{ devHint.email }}</code> / <code>{{ devHint.password }}</code>
76
+ {{ msg.login.devHintLabel }} <code>{{ devHint.email }}</code> /
77
+ <code>{{ devHint.password }}</code>
75
78
  </div>
76
79
  </div>
77
80
 
@@ -86,14 +89,15 @@ import { computed, onMounted, reactive, ref } from 'vue';
86
89
  import { useRouter } from 'vue-router';
87
90
  import type { SetupStatusResponse } from '@saasicat/types';
88
91
 
89
- import { usePublicBoot } from '../use-public-boot.js';
92
+ import { usePublicBoot } from '../vue/use-public-boot.js';
90
93
  import {
91
94
  useSuperAdminBrand,
92
95
  useSuperAdminEndpoints,
93
96
  useSuperAdminHttp,
94
97
  useSuperAdminLoginAdapter,
95
- } from '../use-super-admin-context.js';
96
- import { getJson } from '../http-json.js';
98
+ } from '../vue/use-super-admin-context.js';
99
+ import { getJson } from '../client/http-json.js';
100
+ import { useSaMessages } from '../vue/use-super-admin-i18n.js';
97
101
  import SuperAdminSetupWizard from './SuperAdminSetupWizard.vue';
98
102
 
99
103
  interface Props {
@@ -107,6 +111,7 @@ interface Props {
107
111
  const props = defineProps<Props>();
108
112
 
109
113
  const router = useRouter();
114
+ const msg = useSaMessages('shell');
110
115
  const brand = useSuperAdminBrand();
111
116
  const endpoints = useSuperAdminEndpoints();
112
117
  const adapter = useSuperAdminLoginAdapter();
@@ -161,11 +166,9 @@ function describeError(
161
166
  result: Extract<Awaited<ReturnType<typeof adapter.login>>, { ok: false }>,
162
167
  ): string {
163
168
  if (result.message) return result.message;
164
- if (result.code === 'BAD_CREDENTIALS') return 'E-Mail oder Passwort falsch.';
165
- if (result.code === 'NOT_SUPER_ADMIN') {
166
- return 'Dieser Account hat keine SUPER_ADMIN-Rolle. Bitte Tenant-Frontend nutzen.';
167
- }
168
- return 'Anmeldung fehlgeschlagen.';
169
+ if (result.code === 'BAD_CREDENTIALS') return msg.value.login.errorBadCredentials;
170
+ if (result.code === 'NOT_SUPER_ADMIN') return msg.value.login.errorNotSuperAdmin;
171
+ return msg.value.login.errorFailed;
169
172
  }
170
173
 
171
174
  async function handleSubmit(): Promise<void> {
@@ -182,7 +185,7 @@ async function handleSubmit(): Promise<void> {
182
185
  } catch (err) {
183
186
  const e = err as { response?: { data?: { message?: string; code?: string } } };
184
187
  errorMessage.value =
185
- e.response?.data?.message ?? e.response?.data?.code ?? 'Anmeldung fehlgeschlagen.';
188
+ e.response?.data?.message ?? e.response?.data?.code ?? msg.value.login.errorFailed;
186
189
  } finally {
187
190
  loading.value = false;
188
191
  }