@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
@@ -3,25 +3,28 @@
3
3
  <!-- Head -->
4
4
  <div class="mc-promo-head">
5
5
  <div class="mc-promo-head-text">
6
- <div class="mc-promo-title">Aktionen &amp; Rabatte</div>
7
- <div class="mc-promo-subtitle">
8
- Zeitlich begrenzte Preis-Aktionen — überschreiben den regulären Preis auf der
9
- Pricing-Page (SPEC_V2 §9a).
10
- </div>
6
+ <div class="mc-promo-title">{{ msg.promotionsTab.title }}</div>
7
+ <div class="mc-promo-subtitle">{{ msg.promotionsTab.subtitle }}</div>
11
8
  </div>
12
9
  <div class="mc-promo-stats">
13
- <span class="mc-promo-stat active">{{ counts.active }} aktiv</span>
14
- <span class="mc-promo-stat scheduled">{{ counts.scheduled }} geplant</span>
15
- <span class="mc-promo-stat expired">{{ counts.expired }} abgelaufen</span>
10
+ <span class="mc-promo-stat active">
11
+ {{ counts.active }} {{ msg.promotionsTab.statusActive }}
12
+ </span>
13
+ <span class="mc-promo-stat scheduled">
14
+ {{ counts.scheduled }} {{ msg.promotionsTab.statusScheduled }}
15
+ </span>
16
+ <span class="mc-promo-stat expired">
17
+ {{ counts.expired }} {{ msg.promotionsTab.statusExpired }}
18
+ </span>
16
19
  </div>
17
20
  <button class="mc-promo-add" type="button" :disabled="busy" @click="onAdd">
18
- + Aktion anlegen
21
+ {{ msg.promotionsTab.add }}
19
22
  </button>
20
23
  </div>
21
24
 
22
25
  <!-- Timeline -->
23
26
  <div v-if="promotions.length > 0" class="mc-promo-timeline">
24
- <div class="mc-promo-timeline-head">Aktions-Timeline · gestrichelt = heute</div>
27
+ <div class="mc-promo-timeline-head">{{ msg.promotionsTab.timelineHead }}</div>
25
28
  <div class="mc-promo-timeline-chart" :style="{ height: `${timelineHeight}px` }">
26
29
  <div
27
30
  v-for="t in ticks"
@@ -32,7 +35,7 @@
32
35
  <span class="mc-promo-tick-label">{{ t.label }}</span>
33
36
  </div>
34
37
  <div class="mc-promo-today" :style="{ left: `${todayX}%` }">
35
- <span>heute</span>
38
+ <span>{{ msg.promotionsTab.today }}</span>
36
39
  </div>
37
40
  <div
38
41
  v-for="bar in bars"
@@ -54,8 +57,8 @@
54
57
  </div>
55
58
 
56
59
  <div v-if="promotions.length === 0" class="mc-promo-empty">
57
- Noch keine Aktionen. Über <strong>+ Aktion anlegen</strong> eine zeitgesteuerte
58
- Preis-Aktion erstellen.
60
+ {{ msg.promotionsTab.emptyBefore }} <strong>{{ msg.promotionsTab.add }}</strong>
61
+ {{ msg.promotionsTab.emptyAfter }}
59
62
  </div>
60
63
 
61
64
  <!-- List -->
@@ -72,7 +75,7 @@
72
75
  <div class="mc-promo-row-sub">
73
76
  <span class="mc-promo-typechip">{{ typeChip(p) }}</span>
74
77
  <span v-if="p.appliesTo.length === 0" class="mc-promo-muted">
75
- keine Pläne —
78
+ {{ msg.promotionsTab.noPlans }}
76
79
  </span>
77
80
  <span v-for="k in p.appliesTo" :key="k" class="mc-promo-planchip">
78
81
  {{ k }}
@@ -84,7 +87,8 @@
84
87
  <span class="mc-promo-cycle">
85
88
  {{ cycleLabel(p.billingCycle) }}
86
89
  <template v-if="p.onlyLocales">
87
- · nur {{ p.onlyLocales.join(', ').toUpperCase() }}
90
+ · {{ msg.promotionsTab.onlyPrefix }}
91
+ {{ p.onlyLocales.join(', ').toUpperCase() }}
88
92
  </template>
89
93
  </span>
90
94
  </div>
@@ -97,17 +101,19 @@
97
101
  <div v-if="expandedId === p.id" class="mc-promo-editor">
98
102
  <div class="mc-promo-editor-grid">
99
103
  <div class="mc-promo-editor-col">
100
- <label class="mc-promo-label">Interne Bezeichnung</label>
104
+ <label class="mc-promo-label">
105
+ {{ msg.promotionsTab.internalLabelLabel }}
106
+ </label>
101
107
  <input
102
108
  class="mc-promo-input"
103
109
  :value="p.internalLabel"
104
110
  @change="patch(p, { internalLabel: inputVal($event) })"
105
111
  />
106
112
 
107
- <label class="mc-promo-label">Typ</label>
113
+ <label class="mc-promo-label">{{ common.type }}</label>
108
114
  <div class="mc-promo-typegrid">
109
115
  <button
110
- v-for="t in TYPE_OPTIONS"
116
+ v-for="t in typeOptions"
111
117
  :key="t.id"
112
118
  type="button"
113
119
  class="mc-promo-typeopt"
@@ -118,7 +124,7 @@
118
124
  </button>
119
125
  </div>
120
126
 
121
- <label class="mc-promo-label">Wert</label>
127
+ <label class="mc-promo-label">{{ msg.promotionsTab.valueLabel }}</label>
122
128
  <div class="mc-promo-valrow">
123
129
  <template v-if="p.type === 'percent' || p.type === 'amount'">
124
130
  <input
@@ -128,39 +134,55 @@
128
134
  @change="patch(p, { value: numInput($event) })"
129
135
  />
130
136
  <span class="mc-promo-muted">
131
- {{ p.type === 'percent' ? '% Rabatt' : '€ Festbetrag' }}
137
+ {{
138
+ p.type === 'percent'
139
+ ? msg.promotionsTab.percentUnit
140
+ : msg.promotionsTab.amountUnit
141
+ }}
132
142
  </span>
133
143
  </template>
134
144
  <template v-else-if="p.type === 'intro'">
135
- <span class="mc-promo-muted">für</span>
145
+ <span class="mc-promo-muted">
146
+ {{ msg.promotionsTab.introForPrefix }}
147
+ </span>
136
148
  <input
137
149
  class="mc-promo-input mc-promo-input--sm"
138
150
  type="number"
139
151
  :value="introMonths(p)"
140
152
  @change="patchIntro(p, 'months', numInput($event))"
141
153
  />
142
- <span class="mc-promo-muted">Monate à</span>
154
+ <span class="mc-promo-muted">
155
+ {{ msg.promotionsTab.introMonthsUnit }}
156
+ </span>
143
157
  <input
144
158
  class="mc-promo-input mc-promo-input--sm"
145
159
  type="number"
146
160
  :value="introPrice(p)"
147
161
  @change="patchIntro(p, 'price', numInput($event))"
148
162
  />
149
- <span class="mc-promo-muted">€/Mo</span>
163
+ <span class="mc-promo-muted">
164
+ {{ msg.promotionsTab.introPriceUnit }}
165
+ </span>
150
166
  </template>
151
167
  <template v-else>
152
- <span class="mc-promo-muted">erste</span>
168
+ <span class="mc-promo-muted">
169
+ {{ msg.promotionsTab.freeMonthsPrefix }}
170
+ </span>
153
171
  <input
154
172
  class="mc-promo-input mc-promo-input--sm"
155
173
  type="number"
156
174
  :value="numValue(p)"
157
175
  @change="patch(p, { value: numInput($event) })"
158
176
  />
159
- <span class="mc-promo-muted">Monate gratis</span>
177
+ <span class="mc-promo-muted">
178
+ {{ msg.promotionsTab.freeMonthsUnit }}
179
+ </span>
160
180
  </template>
161
181
  </div>
162
182
 
163
- <label class="mc-promo-label">Gültigkeit</label>
183
+ <label class="mc-promo-label">
184
+ {{ common.validity }}
185
+ </label>
164
186
  <div class="mc-promo-valrow">
165
187
  <input
166
188
  class="mc-promo-input"
@@ -177,10 +199,12 @@
177
199
  />
178
200
  </div>
179
201
 
180
- <label class="mc-promo-label">Abrechnungs-Zyklus</label>
202
+ <label class="mc-promo-label">
203
+ {{ msg.promotionsTab.billingCycleLabel }}
204
+ </label>
181
205
  <div class="mc-promo-typegrid">
182
206
  <button
183
- v-for="c in CYCLE_OPTIONS"
207
+ v-for="c in cycleOptions"
184
208
  :key="c.id"
185
209
  type="button"
186
210
  class="mc-promo-typeopt"
@@ -191,7 +215,9 @@
191
215
  </button>
192
216
  </div>
193
217
 
194
- <label class="mc-promo-label">Priorität</label>
218
+ <label class="mc-promo-label">
219
+ {{ msg.promotionsTab.priorityLabel }}
220
+ </label>
195
221
  <input
196
222
  class="mc-promo-input mc-promo-input--sm"
197
223
  type="number"
@@ -201,7 +227,9 @@
201
227
  </div>
202
228
 
203
229
  <div class="mc-promo-editor-col">
204
- <label class="mc-promo-label">Betroffene Pläne</label>
230
+ <label class="mc-promo-label">
231
+ {{ msg.promotionsTab.appliesToLabel }}
232
+ </label>
205
233
  <div class="mc-promo-planlist">
206
234
  <button
207
235
  v-for="pl in plans"
@@ -216,7 +244,9 @@
216
244
  </button>
217
245
  </div>
218
246
 
219
- <label class="mc-promo-label">Sprach-Beschränkung</label>
247
+ <label class="mc-promo-label">
248
+ {{ msg.promotionsTab.localeRestrictionLabel }}
249
+ </label>
220
250
  <div class="mc-promo-typegrid">
221
251
  <button
222
252
  type="button"
@@ -224,7 +254,7 @@
224
254
  :class="{ active: !p.onlyLocales }"
225
255
  @click="patch(p, { onlyLocales: null })"
226
256
  >
227
- alle Sprachen
257
+ {{ msg.promotionsTab.allLocales }}
228
258
  </button>
229
259
  <button
230
260
  v-for="l in activeLocales"
@@ -234,28 +264,30 @@
234
264
  :class="{ active: p.onlyLocales?.includes(l) }"
235
265
  @click="toggleLocale(p, l)"
236
266
  >
237
- nur {{ l.toUpperCase() }}
267
+ {{ msg.promotionsTab.onlyPrefix }} {{ l.toUpperCase() }}
238
268
  </button>
239
269
  </div>
240
270
 
241
- <label class="mc-promo-label">Übersetzungen</label>
271
+ <label class="mc-promo-label">
272
+ {{ msg.promotionsTab.translationsLabel }}
273
+ </label>
242
274
  <div v-for="l in activeLocales" :key="l" class="mc-promo-i18n-block">
243
275
  <span class="mc-promo-i18n-code">{{ l.toUpperCase() }}</span>
244
276
  <input
245
277
  class="mc-promo-input"
246
- placeholder="Badge-Text"
278
+ :placeholder="msg.promotionsTab.badgePlaceholder"
247
279
  :value="p.i18n?.[l]?.badge || ''"
248
280
  @change="patchI18n(p, l, 'badge', inputVal($event))"
249
281
  />
250
282
  <input
251
283
  class="mc-promo-input"
252
- placeholder="Fineprint (unter dem CTA)"
284
+ :placeholder="msg.promotionsTab.fineprintPlaceholder"
253
285
  :value="p.i18n?.[l]?.fineprint || ''"
254
286
  @change="patchI18n(p, l, 'fineprint', inputVal($event))"
255
287
  />
256
288
  </div>
257
289
 
258
- <label class="mc-promo-label">Farbe</label>
290
+ <label class="mc-promo-label">{{ msg.promotionsTab.colorLabel }}</label>
259
291
  <div class="mc-promo-colors">
260
292
  <button
261
293
  v-for="c in COLORS"
@@ -269,7 +301,7 @@
269
301
  </div>
270
302
 
271
303
  <button class="mc-promo-delete" type="button" @click="onRemove(p)">
272
- Aktion löschen
304
+ {{ msg.promotionsTab.delete }}
273
305
  </button>
274
306
  </div>
275
307
  </div>
@@ -290,6 +322,8 @@ import {
290
322
  type PromotionType,
291
323
  type UpdatePromotionData,
292
324
  } from '@saasicat/types';
325
+ import { formatMessage } from '../client/i18n/format.js';
326
+ import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
293
327
 
294
328
  // Promotions tab of the Marketing Catalog (SPEC_V2 §9a). Standalone child
295
329
  // component — the MarketingCatalogPage wires `usePromotions` and passes the
@@ -309,20 +343,24 @@ const props = defineProps<{
309
343
  projectKey: string;
310
344
  }>();
311
345
 
346
+ const msg = useSaMessages('marketing');
347
+ const common = useSaMessages('common');
348
+ const { intlLocale } = useSuperAdminI18n();
349
+
312
350
  const busy = computed(() => props.busy ?? false);
313
351
  const expandedId = ref<string | null>(null);
314
352
 
315
- const TYPE_OPTIONS: Array<{ id: PromotionType; label: string }> = [
316
- { id: 'percent', label: 'Prozent-Rabatt' },
317
- { id: 'amount', label: 'Festbetrag' },
318
- { id: 'intro', label: 'Einführungspreis' },
319
- { id: 'freeMonths', label: 'Gratis-Monate' },
320
- ];
321
- const CYCLE_OPTIONS: Array<{ id: PromotionBillingCycle; label: string }> = [
322
- { id: 'monthly', label: 'Monatlich' },
323
- { id: 'yearly', label: 'Jährlich' },
324
- { id: 'both', label: 'Beide' },
325
- ];
353
+ const typeOptions = computed<Array<{ id: PromotionType; label: string }>>(() => [
354
+ { id: 'percent', label: msg.value.promotionsTab.typePercent },
355
+ { id: 'amount', label: msg.value.promotionsTab.typeAmount },
356
+ { id: 'intro', label: msg.value.promotionsTab.typeIntro },
357
+ { id: 'freeMonths', label: msg.value.promotionsTab.typeFreeMonths },
358
+ ]);
359
+ const cycleOptions = computed<Array<{ id: PromotionBillingCycle; label: string }>>(() => [
360
+ { id: 'monthly', label: common.value.monthly },
361
+ { id: 'yearly', label: common.value.yearly },
362
+ { id: 'both', label: common.value.both },
363
+ ]);
326
364
  const COLORS = ['#10b981', '#dc2626', '#f59e0b', '#2563eb', '#7c3aed', '#0f172a'];
327
365
 
328
366
  const today = new Date();
@@ -331,7 +369,9 @@ function statusOf(p: PromotionRow): PromotionStatus {
331
369
  return promoStatus(p, today);
332
370
  }
333
371
  function statusLabel(s: PromotionStatus): string {
334
- return s === 'active' ? 'aktiv' : s === 'scheduled' ? 'geplant' : 'abgelaufen';
372
+ if (s === 'active') return msg.value.promotionsTab.statusActive;
373
+ if (s === 'scheduled') return msg.value.promotionsTab.statusScheduled;
374
+ return msg.value.promotionsTab.statusExpired;
335
375
  }
336
376
 
337
377
  const counts = computed(() => ({
@@ -353,13 +393,20 @@ function typeChip(p: PromotionRow): string {
353
393
  if (p.type === 'percent' && typeof p.value === 'number') return `−${p.value}%`;
354
394
  if (p.type === 'amount' && typeof p.value === 'number') return `−${p.value} €`;
355
395
  if (p.type === 'intro' && typeof p.value === 'object') {
356
- return `${p.value.price} € · ${p.value.months} Mo.`;
396
+ return formatMessage(msg.value.promotionsTab.chipIntro, {
397
+ price: p.value.price,
398
+ months: p.value.months,
399
+ });
400
+ }
401
+ if (p.type === 'freeMonths' && typeof p.value === 'number') {
402
+ return formatMessage(msg.value.promotionsTab.chipFreeMonths, { months: p.value });
357
403
  }
358
- if (p.type === 'freeMonths' && typeof p.value === 'number') return `${p.value} Mo. gratis`;
359
404
  return p.type;
360
405
  }
361
406
  function cycleLabel(c: PromotionBillingCycle): string {
362
- return c === 'both' ? 'mtl. & jährl.' : c === 'monthly' ? 'mtl.' : 'jährl.';
407
+ if (c === 'both') return msg.value.promotionsTab.cycleBothShort;
408
+ if (c === 'monthly') return msg.value.promotionsTab.cycleMonthlyShort;
409
+ return msg.value.promotionsTab.cycleYearlyShort;
363
410
  }
364
411
 
365
412
  // ─── Timeline ───
@@ -386,6 +433,12 @@ function pct(d: Date | string): number {
386
433
  return Math.max(0, Math.min(100, v));
387
434
  }
388
435
  const todayX = computed(() => pct(today));
436
+ function tickLabel(d: Date): string {
437
+ const month = new Date(d.getFullYear(), d.getMonth(), 1).toLocaleDateString(intlLocale.value, {
438
+ month: 'short',
439
+ });
440
+ return `${month} ${d.getFullYear()}`;
441
+ }
389
442
  const ticks = computed(() => {
390
443
  const out: Array<{ x: number; label: string }> = [];
391
444
  const start = axisStart.value;
@@ -393,10 +446,7 @@ const ticks = computed(() => {
393
446
  const d = new Date(Date.UTC(start.getFullYear(), start.getMonth(), 1));
394
447
  while (d <= end) {
395
448
  if (d.getMonth() % 3 === 0) {
396
- out.push({
397
- x: pct(d),
398
- label: `${['Jan', 'Apr', 'Jul', 'Okt'][d.getMonth() / 3]} ${d.getFullYear()}`,
399
- });
449
+ out.push({ x: pct(d), label: tickLabel(d) });
400
450
  }
401
451
  d.setMonth(d.getMonth() + 1);
402
452
  }
@@ -492,7 +542,7 @@ function patchI18n(
492
542
  async function onAdd(): Promise<void> {
493
543
  const created = await props.create({
494
544
  projectKey: props.projectKey,
495
- internalLabel: 'Neue Aktion',
545
+ internalLabel: msg.value.promotionsTab.newPromotionLabel,
496
546
  type: 'percent',
497
547
  value: 10,
498
548
  appliesTo: [],
@@ -507,7 +557,10 @@ async function onAdd(): Promise<void> {
507
557
  }
508
558
 
509
559
  async function onRemove(p: PromotionRow): Promise<void> {
510
- if (!window.confirm(`Aktion „${p.internalLabel}“ wirklich löschen?`)) return;
560
+ const question = formatMessage(msg.value.promotionsTab.deleteConfirm, {
561
+ label: p.internalLabel,
562
+ });
563
+ if (!window.confirm(question)) return;
511
564
  if (expandedId.value === p.id) expandedId.value = null;
512
565
  await props.remove(p.id);
513
566
  }
@@ -7,15 +7,10 @@
7
7
  <q-card class="mfa-card">
8
8
  <q-card-section class="header">
9
9
  <q-icon name="lock" size="22px" color="amber-9" />
10
- <div class="text-h6">Multi-Faktor-Bestätigung</div>
10
+ <div class="text-h6">{{ msg.mfa.title }}</div>
11
11
  </q-card-section>
12
12
  <q-card-section>
13
- <p class="text-body2 q-mb-sm">
14
- {{
15
- description ??
16
- 'Diese Aktion ist sicherheitskritisch. Bitte 6-stelligen TOTP-Code aus Authenticator eingeben.'
17
- }}
18
- </p>
13
+ <p class="text-body2 q-mb-sm">{{ resolvedDescription }}</p>
19
14
  <q-input
20
15
  v-model="code"
21
16
  autofocus
@@ -23,7 +18,7 @@
23
18
  maxlength="6"
24
19
  outlined
25
20
  dense
26
- label="TOTP-Code"
21
+ :label="msg.mfa.codeLabel"
27
22
  :error="!!error"
28
23
  :error-message="error"
29
24
  />
@@ -36,12 +31,12 @@
36
31
  </slot>
37
32
  </q-card-section>
38
33
  <q-card-actions align="right">
39
- <q-btn flat label="Abbrechen" v-close-popup />
34
+ <q-btn flat :label="common.cancel" v-close-popup />
40
35
  <q-btn
41
36
  unelevated
42
37
  color="amber-9"
43
38
  text-color="black"
44
- label="Bestätigen"
39
+ :label="common.confirm"
45
40
  :disable="code.length !== 6"
46
41
  @click="onConfirm"
47
42
  />
@@ -51,7 +46,8 @@
51
46
  </template>
52
47
 
53
48
  <script setup lang="ts">
54
- import { ref, watch } from 'vue';
49
+ import { computed, ref, watch } from 'vue';
50
+ import { useSaMessages } from '../vue/use-super-admin-i18n.js';
55
51
 
56
52
  // Cross-cutting MFA confirmation dialog. An app-specific setup hint
57
53
  // (e.g. "MFA setup via CLI: ...") is shown via the `setupHint` prop or
@@ -69,6 +65,10 @@ const emit = defineEmits<{
69
65
  (e: 'confirm', code: string): void;
70
66
  }>();
71
67
 
68
+ const msg = useSaMessages('shell');
69
+ const common = useSaMessages('common');
70
+ const resolvedDescription = computed(() => props.description ?? msg.value.mfa.defaultDescription);
71
+
72
72
  const code = ref('');
73
73
 
74
74
  watch(
@@ -11,12 +11,13 @@
11
11
  size="22px"
12
12
  :color="isDangerous ? 'negative' : 'primary'"
13
13
  />
14
- <div class="text-h6">{{ def?.label ?? 'Aktion bestätigen' }}</div>
14
+ <div class="text-h6">{{ def?.label ?? msg.actions.confirmTitle }}</div>
15
15
  </q-card-section>
16
16
 
17
17
  <q-card-section v-if="def">
18
18
  <p class="tenant-action-confirm__lead">
19
- Aktion <strong>„{{ def.label }}"</strong> für Tenant
19
+ {{ msg.actions.leadAction }} <strong>„{{ def.label }}"</strong>
20
+ {{ msg.actions.leadForTenant }}
20
21
  <strong>„{{ row?.name ?? row?.slug ?? '–' }}"</strong>.
21
22
  </p>
22
23
 
@@ -24,7 +25,7 @@
24
25
  gate against accidentally clicking destructive actions. -->
25
26
  <template v-if="needsTypedSlug">
26
27
  <p class="tenant-action-confirm__hint">
27
- Bestätige durch Eintippen des Slugs
28
+ {{ msg.actions.typedSlugHint }}
28
29
  <code>{{ row?.slug }}</code
29
30
  >:
30
31
  </p>
@@ -51,9 +52,7 @@
51
52
  :min="dateMin"
52
53
  :label="dateLabel"
53
54
  :error="dateError"
54
- :error-message="
55
- dateError ? 'Bitte ein Datum in der Zukunft wählen.' : undefined
56
- "
55
+ :error-message="dateError ? msg.actions.dateFutureError : undefined"
57
56
  />
58
57
  </template>
59
58
 
@@ -69,22 +68,18 @@
69
68
  autogrow
70
69
  class="q-mt-md"
71
70
  :autofocus="!needsTypedSlug && !needsDate"
72
- label="Grund (Pflicht für Audit-Log, mindestens 5 Zeichen)"
71
+ :label="msg.actions.reasonLabel"
73
72
  :error="reasonError"
74
- :error-message="
75
- reasonError
76
- ? 'Bitte einen Grund mit mindestens 5 Zeichen angeben.'
77
- : undefined
78
- "
73
+ :error-message="reasonError ? msg.actions.reasonError : undefined"
79
74
  />
80
75
  </q-card-section>
81
76
 
82
77
  <q-card-actions align="right">
83
- <q-btn flat label="Abbrechen" @click="onCancel" />
78
+ <q-btn flat :label="common.cancel" @click="onCancel" />
84
79
  <q-btn
85
80
  unelevated
86
81
  :color="isDangerous ? 'negative' : 'primary'"
87
- :label="def?.label ?? 'Bestätigen'"
82
+ :label="def?.label ?? common.confirm"
88
83
  :disable="!canSubmit"
89
84
  @click="onConfirm"
90
85
  />
@@ -96,6 +91,8 @@
96
91
  <script setup lang="ts">
97
92
  import { computed, ref, watch } from 'vue';
98
93
  import type { TenantActionDef, TenantDto } from '@saasicat/types';
94
+ import { formatMessage } from '../client/i18n/format.js';
95
+ import { useSaMessages } from '../vue/use-super-admin-i18n.js';
99
96
 
100
97
  // Platform-standard confirm dialog for tenant actions. Covers both confirm
101
98
  // paths from the manifest in a single UI:
@@ -128,6 +125,9 @@ const emit = defineEmits<{
128
125
  (e: 'cancel'): void;
129
126
  }>();
130
127
 
128
+ const msg = useSaMessages('tenants');
129
+ const common = useSaMessages('common');
130
+
131
131
  const slugInput = ref('');
132
132
  const reasonInput = ref('');
133
133
  const dateInput = ref('');
@@ -136,7 +136,9 @@ const reasonError = ref(false);
136
136
  const dateError = ref(false);
137
137
 
138
138
  const slugErrorMessage = computed(() =>
139
- slugError.value ? `Muss exakt „${props.row?.slug}“ sein` : undefined,
139
+ slugError.value
140
+ ? formatMessage(msg.value.actions.slugMismatch, { slug: props.row?.slug ?? '' })
141
+ : undefined,
140
142
  );
141
143
 
142
144
  watch(
@@ -161,8 +163,8 @@ const needsDate = computed(() => props.def?.confirmType === 'date');
161
163
  const dateMin = computed(() => new Date().toISOString().slice(0, 10));
162
164
  const dateLabel = computed(() => {
163
165
  const key = props.def?.actionKey ?? '';
164
- if (key.endsWith('.extend')) return 'Verlängern bis (Datum)';
165
- return 'Datum';
166
+ if (key.endsWith('.extend')) return msg.value.actions.extendUntilLabel;
167
+ return common.value.date;
166
168
  });
167
169
 
168
170
  const isDangerous = computed(() => {