@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,11 +3,11 @@
3
3
  <!-- Header -->
4
4
  <div class="pr-head">
5
5
  <div class="pr-head-text">
6
- <h2 class="pr-title">Review &amp; Publish</h2>
6
+ <h2 class="pr-title">{{ msg.review.title }}</h2>
7
7
  <p class="pr-sub">
8
- Letzte Prüfung, bevor
8
+ {{ msg.review.subtitleLead }}
9
9
  <code class="pr-mono">{{ plan.planKey }}@v{{ version.version }}</code>
10
- live geht.
10
+ {{ msg.review.subtitleTail }}
11
11
  </p>
12
12
  </div>
13
13
  <div class="pr-head-actions">
@@ -29,7 +29,7 @@
29
29
  <path d="M5 12h14M13 5l7 7-7 7" />
30
30
  </svg>
31
31
  </span>
32
- <span>Zurück</span>
32
+ <span>{{ common.back }}</span>
33
33
  </button>
34
34
  <button
35
35
  class="pr-btn"
@@ -37,13 +37,13 @@
37
37
  :disabled="publishing || saving"
38
38
  @click="$emit('saveAndExit')"
39
39
  >
40
- {{ saving ? 'Wird gespeichert…' : 'Als Draft speichern' }}
40
+ {{ saving ? msg.review.saving : msg.review.saveAsDraft }}
41
41
  </button>
42
42
  <button
43
43
  class="pr-btn pr-btn--primary"
44
44
  type="button"
45
45
  :disabled="!canPublish || publishing || saving"
46
- :title="canPublish ? undefined : 'Publish-Checkliste noch nicht vollständig'"
46
+ :title="canPublish ? undefined : msg.review.publishBlocked"
47
47
  @click="onPublish"
48
48
  >
49
49
  <svg
@@ -56,9 +56,7 @@
56
56
  >
57
57
  <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
58
58
  </svg>
59
- <span>{{
60
- publishing ? 'Wird veröffentlicht…' : `Publish v${version.version}`
61
- }}</span>
59
+ <span>{{ publishing ? msg.review.publishing : publishLabel }}</span>
62
60
  </button>
63
61
  </div>
64
62
  </div>
@@ -69,100 +67,108 @@
69
67
  <!-- Left column -->
70
68
  <div class="pr-col">
71
69
  <div class="pr-card">
72
- <h3 class="pr-card-title">Stammdaten</h3>
70
+ <h3 class="pr-card-title">{{ msg.review.cardMasterData }}</h3>
73
71
  <div class="pr-row">
74
- <div class="pr-label">Plan-Key</div>
72
+ <div class="pr-label">{{ msg.review.labelPlanKey }}</div>
75
73
  <div class="pr-val pr-mono">{{ plan.planKey }}</div>
76
74
  </div>
77
75
  <div class="pr-row">
78
- <div class="pr-label">Anzeigename</div>
76
+ <div class="pr-label">{{ msg.review.labelDisplayName }}</div>
79
77
  <div class="pr-val">{{ plan.label }}</div>
80
78
  </div>
81
79
  <div class="pr-row">
82
- <div class="pr-label">Beschreibung</div>
80
+ <div class="pr-label">{{ common.description }}</div>
83
81
  <div class="pr-val">
84
82
  <template v-if="plan.description">{{ plan.description }}</template>
85
- <i v-else class="pr-missing">fehlt</i>
83
+ <i v-else class="pr-missing">{{ msg.review.missing }}</i>
86
84
  </div>
87
85
  </div>
88
86
  </div>
89
87
 
90
88
  <div class="pr-card">
91
- <h3 class="pr-card-title">
92
- Version v{{ version.version }} · Pricing &amp; Sichtbarkeit
93
- </h3>
89
+ <h3 class="pr-card-title">{{ pricingCardTitle }}</h3>
94
90
  <div class="pr-row">
95
- <div class="pr-label">Gültig ab</div>
91
+ <div class="pr-label">{{ msg.review.labelValidFrom }}</div>
96
92
  <div class="pr-val">
97
93
  <template v-if="version.validFrom">{{
98
94
  version.validFrom.slice(0, 10)
99
95
  }}</template>
100
- <i v-else class="pr-missing">fehlt — beim Publish Pflicht</i>
96
+ <i v-else class="pr-missing">{{
97
+ msg.review.missingRequiredOnPublish
98
+ }}</i>
101
99
  </div>
102
100
  </div>
103
101
  <div class="pr-row">
104
- <div class="pr-label">Gültig bis</div>
102
+ <div class="pr-label">{{ msg.review.labelValidUntil }}</div>
105
103
  <div class="pr-val">
106
104
  <template v-if="version.validUntil">{{
107
105
  version.validUntil.slice(0, 10)
108
106
  }}</template>
109
- <span v-else class="pr-inf">∞ unbegrenzt</span>
107
+ <span v-else class="pr-inf">{{ msg.review.unlimited }}</span>
110
108
  </div>
111
109
  </div>
112
110
  <div class="pr-row">
113
- <div class="pr-label">Preis monatlich</div>
111
+ <div class="pr-label">{{ msg.review.labelMonthlyPrice }}</div>
114
112
  <div class="pr-val">
115
113
  <template v-if="hasMonthly">{{
116
114
  formatMoney(version.monthlyNet)
117
115
  }}</template>
118
- <i v-else class="pr-missing">fehlt</i>
116
+ <i v-else class="pr-missing">{{ msg.review.missing }}</i>
119
117
  </div>
120
118
  </div>
121
119
  <div class="pr-row">
122
- <div class="pr-label">Preis jährlich</div>
120
+ <div class="pr-label">{{ msg.review.labelYearlyPrice }}</div>
123
121
  <div class="pr-val">{{ formatMoney(version.yearlyNet) }}</div>
124
122
  </div>
125
123
  <div class="pr-row">
126
- <div class="pr-label">Public-Catalog</div>
124
+ <div class="pr-label">{{ msg.review.labelPublicCatalog }}</div>
127
125
  <div class="pr-val">
128
- <span v-if="version.marketed" class="pr-chip pr-chip--live"
129
- >Im Catalog sichtbar</span
130
- >
131
- <span v-else class="pr-chip pr-chip--muted">Ausgeblendet</span>
126
+ <span v-if="version.marketed" class="pr-chip pr-chip--live">{{
127
+ msg.review.visibleInCatalog
128
+ }}</span>
129
+ <span v-else class="pr-chip pr-chip--muted">{{
130
+ msg.review.hiddenFromCatalog
131
+ }}</span>
132
132
  </div>
133
133
  </div>
134
134
  </div>
135
135
 
136
136
  <div class="pr-card">
137
- <h3 class="pr-card-title">Komponenten · {{ componentTotal }} insgesamt</h3>
137
+ <h3 class="pr-card-title">{{ componentsCardTitle }}</h3>
138
138
  <div class="pr-components">
139
139
  <div class="pr-comp-col">
140
140
  <div class="pr-comp-head pr-comp-head--quota">
141
- Quotas · {{ quotaList.length }}
141
+ {{ quotaSectionTitle }}
142
142
  </div>
143
143
  <div v-for="q in quotaList" :key="q.key" class="pr-comp-item">
144
144
  <span>{{ q.label }}</span>
145
145
  <b>{{ q.value }}{{ q.unit ? ' ' + q.unit : '' }}</b>
146
146
  </div>
147
- <div v-if="quotaList.length === 0" class="pr-comp-empty">keine</div>
147
+ <div v-if="quotaList.length === 0" class="pr-comp-empty">
148
+ {{ msg.review.componentEmpty }}
149
+ </div>
148
150
  </div>
149
151
  <div class="pr-comp-col">
150
152
  <div class="pr-comp-head pr-comp-head--feature">
151
- Features · {{ featureList.length }}
153
+ {{ featureSectionTitle }}
152
154
  </div>
153
155
  <div v-for="f in featureList" :key="f.key" class="pr-comp-item">
154
156
  <span>{{ f.label }}</span>
155
157
  </div>
156
- <div v-if="featureList.length === 0" class="pr-comp-empty">keine</div>
158
+ <div v-if="featureList.length === 0" class="pr-comp-empty">
159
+ {{ msg.review.componentEmpty }}
160
+ </div>
157
161
  </div>
158
162
  <div class="pr-comp-col">
159
163
  <div class="pr-comp-head pr-comp-head--bundle">
160
- Bundles · {{ bundleList.length }}
164
+ {{ bundleSectionTitle }}
161
165
  </div>
162
166
  <div v-for="b in bundleList" :key="b.key" class="pr-comp-item">
163
167
  <span>{{ b.label }}</span>
164
168
  </div>
165
- <div v-if="bundleList.length === 0" class="pr-comp-empty">keine</div>
169
+ <div v-if="bundleList.length === 0" class="pr-comp-empty">
170
+ {{ msg.review.componentEmpty }}
171
+ </div>
166
172
  </div>
167
173
  </div>
168
174
  </div>
@@ -171,19 +177,18 @@
171
177
  <!-- Right column -->
172
178
  <div class="pr-col">
173
179
  <div class="pr-card">
174
- <h3 class="pr-card-title">Change-Note <span class="pr-req">*</span></h3>
175
- <p class="pr-card-hint">
176
- Wird im Audit-Log gespeichert und mit der Versions-History angezeigt.
177
- </p>
180
+ <h3 class="pr-card-title">
181
+ {{ msg.review.cardChangeNote }} <span class="pr-req">*</span>
182
+ </h3>
183
+ <p class="pr-card-hint">{{ msg.review.changeNoteHint }}</p>
178
184
  <div v-if="version.changeNote" class="pr-note">{{ version.changeNote }}</div>
179
185
  <div v-else class="pr-note pr-note--missing">
180
- Keine Change-Note — im Editor nachtragen (Pflicht beim Publish,
181
- Vertragsschutz&nbsp;P3).
186
+ {{ msg.review.changeNoteMissing }}
182
187
  </div>
183
188
  </div>
184
189
 
185
190
  <div class="pr-card">
186
- <h3 class="pr-card-title">Publish-Checkliste</h3>
191
+ <h3 class="pr-card-title">{{ msg.review.cardChecklist }}</h3>
187
192
  <div class="pr-checks">
188
193
  <div
189
194
  v-for="c in checks"
@@ -223,46 +228,28 @@
223
228
  </div>
224
229
 
225
230
  <div v-if="predecessor" class="pr-card pr-card--regress">
226
- <h3 class="pr-card-title">Vertragsschutz P3 — Regression</h3>
227
- <p class="pr-card-hint">
228
- Wenn diese Version regressiv ist (Feature entfernt, Quota gesenkt, Preis
229
- erhöht), blockiert das Backend den Publish. Mit Force-Publish wird die
230
- Regression bewusst freigegeben und löst die Notification-Welle aus.
231
- </p>
231
+ <h3 class="pr-card-title">{{ msg.review.cardRegression }}</h3>
232
+ <p class="pr-card-hint">{{ msg.review.regressionHint }}</p>
232
233
  <label class="pr-toggle">
233
234
  <input v-model="forceRegressive" type="checkbox" />
234
- <span>Force-Publish auch bei Regression</span>
235
+ <span>{{ msg.review.forceRegressive }}</span>
235
236
  </label>
236
237
  </div>
237
238
 
238
239
  <div class="pr-card">
239
- <h3 class="pr-card-title">Preis 0,00</h3>
240
- <p class="pr-card-hint">
241
- Standardmäßig blockt das Backend einen Publish mit Preis 0,00
242
- (Schutz gegen Seed-Platzhalter). Für bewusst kostenlose
243
- Sonderverträge hier freigeben.
244
- </p>
240
+ <h3 class="pr-card-title">{{ msg.review.cardZeroPrice }}</h3>
241
+ <p class="pr-card-hint">{{ msg.review.zeroPriceHint }}</p>
245
242
  <label class="pr-toggle">
246
243
  <input v-model="allowZeroPrice" type="checkbox" />
247
- <span>Preis 0,00 bewusst zulassen</span>
244
+ <span>{{ msg.review.allowZeroPrice }}</span>
248
245
  </label>
249
246
  </div>
250
247
 
251
248
  <div class="pr-card pr-card--impact">
252
- <h3 class="pr-card-title">Tenant-Impact bei Publish</h3>
249
+ <h3 class="pr-card-title">{{ msg.review.cardTenantImpact }}</h3>
253
250
  <div class="pr-impact">
254
251
  <div class="pr-impact-num">{{ tenantImpactCount }}</div>
255
- <div class="pr-impact-text">
256
- <template v-if="tenantImpactCount === 0">
257
- Aktuell sind keine Mandanten betroffen. Sobald die Version live ist,
258
- können Mandanten den Plan im Catalog buchen.
259
- </template>
260
- <template v-else>
261
- {{ tenantImpactCount }} Mandant(en) auf der aktuellen Live-Version
262
- bleiben per Bestandsschutz (P1) auf ihrer Version — neue Buchungen
263
- laufen auf v{{ version.version }}.
264
- </template>
265
- </div>
252
+ <div class="pr-impact-text">{{ impactText }}</div>
266
253
  </div>
267
254
  </div>
268
255
  </div>
@@ -273,6 +260,9 @@
273
260
  <script setup lang="ts">
274
261
  import { computed, ref } from 'vue';
275
262
  import type { PlanRow, PlanVersionRow } from '@saasicat/types';
263
+ import { formatMessage } from '../../client/i18n/format.js';
264
+ import { formatCurrency } from '../../client/i18n/currency.js';
265
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
276
266
 
277
267
  // PlanReview — step 3 of the plan wizard (SPEC_V2 §6, plan simulation
278
268
  // "Review & Publish"). Shows the saved draft read-only, checks the
@@ -330,9 +320,21 @@ const emit = defineEmits<{
330
320
  (e: 'publish', payload: { forceRegressive: boolean; allowZeroPrice: boolean }): void;
331
321
  }>();
332
322
 
323
+ const msg = useSaMessages('plans');
324
+ const { locale } = useSuperAdminI18n();
325
+ const common = useSaMessages('common');
326
+
333
327
  const forceRegressive = ref(false);
334
328
  const allowZeroPrice = ref(false);
335
329
 
330
+ const publishLabel = computed(() =>
331
+ formatMessage(msg.value.review.publishVersion, { version: props.version.version }),
332
+ );
333
+
334
+ const pricingCardTitle = computed(() =>
335
+ formatMessage(msg.value.review.cardPricing, { version: props.version.version }),
336
+ );
337
+
336
338
  function quotasOf(v: PlanVersionRow): Record<string, number> {
337
339
  if (v.quotas && Object.keys(v.quotas).length > 0) return v.quotas;
338
340
  const legacy: Record<string, number> = {};
@@ -369,6 +371,28 @@ const componentTotal = computed(
369
371
  () => quotaList.value.length + featureList.value.length + bundleList.value.length,
370
372
  );
371
373
 
374
+ const componentsCardTitle = computed(() =>
375
+ formatMessage(msg.value.review.cardComponents, { total: componentTotal.value }),
376
+ );
377
+ const quotaSectionTitle = computed(() =>
378
+ formatMessage(msg.value.review.componentQuotas, { count: quotaList.value.length }),
379
+ );
380
+ const featureSectionTitle = computed(() =>
381
+ formatMessage(msg.value.review.componentFeatures, { count: featureList.value.length }),
382
+ );
383
+ const bundleSectionTitle = computed(() =>
384
+ formatMessage(msg.value.review.componentBundles, { count: bundleList.value.length }),
385
+ );
386
+
387
+ const impactText = computed(() =>
388
+ props.tenantImpactCount === 0
389
+ ? msg.value.review.impactNone
390
+ : formatMessage(msg.value.review.impactSome, {
391
+ count: props.tenantImpactCount,
392
+ version: props.version.version,
393
+ }),
394
+ );
395
+
372
396
  // Valid price — even 0 € (free plan) counts as set; only an
373
397
  // invalid/empty value counts as "missing".
374
398
  const hasMonthly = computed(() => {
@@ -387,9 +411,8 @@ const validFromAfterPredecessor = computed(() => {
387
411
  function formatMoney(raw: string | number): string {
388
412
  const num = typeof raw === 'string' ? Number(raw) : raw;
389
413
  if (!Number.isFinite(num)) return String(raw);
390
- if (num === 0) return 'Kostenlos';
391
- if (Number.isInteger(num)) return `${num} €`;
392
- return `${num.toFixed(2).replace('.', ',')} €`;
414
+ if (num === 0) return msg.value.review.priceFree;
415
+ return formatCurrency(num, locale.value);
393
416
  }
394
417
 
395
418
  interface Check {
@@ -399,33 +422,35 @@ interface Check {
399
422
  }
400
423
 
401
424
  const checks = computed<Check[]>(() => [
402
- { id: 'key', label: 'Plan-Key vergeben', ok: Boolean(props.plan.planKey) },
403
- { id: 'label', label: 'Anzeigename vorhanden', ok: Boolean(props.plan.label) },
425
+ { id: 'key', label: msg.value.review.checkPlanKey, ok: Boolean(props.plan.planKey) },
426
+ { id: 'label', label: msg.value.review.checkDisplayName, ok: Boolean(props.plan.label) },
404
427
  {
405
428
  id: 'quota',
406
- label: 'Mindestens 1 Quota zugewiesen',
429
+ label: msg.value.review.checkQuota,
407
430
  ok: quotaList.value.length > 0,
408
431
  },
409
432
  {
410
433
  id: 'feature',
411
- label: 'Mindestens 1 Feature zugewiesen',
434
+ label: msg.value.review.checkFeature,
412
435
  ok: featureList.value.length > 0,
413
436
  },
414
437
  {
415
438
  id: 'validFrom',
416
- label: '„Gültig ab" gesetzt (Pflicht beim Publish)',
439
+ label: msg.value.review.checkValidFrom,
417
440
  ok: Boolean(props.version.validFrom),
418
441
  },
419
442
  {
420
443
  id: 'validFromOrder',
421
444
  label: props.predecessor
422
- ? `„Gültig ab" liegt nach Vorgänger v${props.predecessor.version}`
423
- : '„Gültig ab" liegt nach der Vorgänger-Version',
445
+ ? formatMessage(msg.value.review.checkValidFromAfterPredecessor, {
446
+ version: props.predecessor.version,
447
+ })
448
+ : msg.value.review.checkValidFromAfterPrevious,
424
449
  ok: validFromAfterPredecessor.value,
425
450
  },
426
451
  {
427
452
  id: 'changeNote',
428
- label: 'Change-Note vorhanden (Vertragsschutz P3)',
453
+ label: msg.value.review.checkChangeNote,
429
454
  ok: Boolean(props.version.changeNote && props.version.changeNote.trim()),
430
455
  },
431
456
  ]);
@@ -2,8 +2,8 @@
2
2
  <section class="pve-col pve-preview">
3
3
  <div class="pve-col-header pve-col-header--preview">
4
4
  <div>
5
- <div class="pve-col-title">Live-Vorschau · Public-Catalog</div>
6
- <div class="pve-col-sub">So sehen Interessenten den Plan</div>
5
+ <div class="pve-col-title">{{ msg.catalogPreview.title }}</div>
6
+ <div class="pve-col-sub">{{ msg.catalogPreview.subtitle }}</div>
7
7
  </div>
8
8
  <div class="pve-preview-toggle">
9
9
  <button
@@ -14,14 +14,14 @@
14
14
  ]"
15
15
  @click="$emit('update:previewMode', 'desktop')"
16
16
  >
17
- Desktop
17
+ {{ msg.catalogPreview.desktop }}
18
18
  </button>
19
19
  <button
20
20
  type="button"
21
21
  :class="['pve-btn pve-btn--sm', { 'pve-btn--ghost': previewMode !== 'mobile' }]"
22
22
  @click="$emit('update:previewMode', 'mobile')"
23
23
  >
24
- Mobil
24
+ {{ msg.catalogPreview.mobile }}
25
25
  </button>
26
26
  </div>
27
27
  </div>
@@ -34,22 +34,22 @@
34
34
  <div class="pve-prev-url">{{ catalogUrl }}</div>
35
35
  </div>
36
36
  <div class="pve-prev-body">
37
- <div class="pve-prev-eyebrow">Unsere Pläne · Vorschau Draft v{{ version }}</div>
37
+ <div class="pve-prev-eyebrow">{{ eyebrow }}</div>
38
38
  <div class="pve-prev-title">{{ planDisplayName }}</div>
39
39
  <div class="pve-prev-desc">
40
- {{ changeNote || 'Plan-Beschreibung wird aus der Change-Note generiert.' }}
40
+ {{ changeNote || msg.catalogPreview.descriptionFallback }}
41
41
  </div>
42
42
  <div class="pve-prev-price">
43
43
  <span class="pve-prev-price-big">{{ formattedMonthly }}</span>
44
- <span class="pve-prev-price-unit">/ Monat</span>
44
+ <span class="pve-prev-price-unit">{{ msg.catalogPreview.perMonth }}</span>
45
45
  </div>
46
46
  <div v-if="yearlySavingsLabel" class="pve-prev-price-yearly">
47
- oder {{ formattedYearly }} / Jahr · {{ yearlySavingsLabel }}
47
+ {{ yearlyAlternative }}
48
48
  </div>
49
49
 
50
- <button type="button" class="pve-prev-cta">Jetzt 30 Tage testen</button>
50
+ <button type="button" class="pve-prev-cta">{{ msg.catalogPreview.cta }}</button>
51
51
 
52
- <div class="pve-prev-sep">Enthalten</div>
52
+ <div class="pve-prev-sep">{{ msg.catalogPreview.included }}</div>
53
53
  <ul class="pve-prev-list">
54
54
  <li v-for="row in selectedQuotaList" :key="`pq-${row.quotaKey}`">
55
55
  <span class="pve-prev-tick" aria-hidden="true">
@@ -86,16 +86,14 @@
86
86
  v-if="sortedSelectedFeatures.length === 0 && selectedQuotaList.length === 0"
87
87
  class="pve-prev-empty"
88
88
  >
89
- Noch keine Inhalte zugewiesen.
89
+ {{ msg.catalogPreview.emptyContents }}
90
90
  </li>
91
91
  </ul>
92
92
 
93
93
  <div class="pve-prev-foot">
94
- Marketing-Catalog ·
94
+ {{ msg.catalogPreview.footerLabel }} ·
95
95
  <code class="pve-mono pve-mono--xs">{{ planKey }}@v{{ version }}</code>
96
- <template v-if="changeNote">
97
- · Change-Note „{{ changeNote }}" wird bei Publish veröffentlicht.
98
- </template>
96
+ <template v-if="changeNote"> · {{ footerChangeNote }} </template>
99
97
  </div>
100
98
  </div>
101
99
  </div>
@@ -114,10 +112,8 @@
114
112
  <path d="M5 13l4 4L19 7" />
115
113
  </svg>
116
114
  </span>
117
- <span class="pve-prev-validate-title">Publish-Checkliste</span>
118
- <span class="pve-prev-validate-count"
119
- >{{ checklistOkCount }} / {{ checklist.length }} ok</span
120
- >
115
+ <span class="pve-prev-validate-title">{{ msg.catalogPreview.checklistTitle }}</span>
116
+ <span class="pve-prev-validate-count">{{ checklistCountLabel }}</span>
121
117
  </div>
122
118
  <div
123
119
  v-for="item in checklist"
@@ -158,9 +154,12 @@
158
154
  </template>
159
155
 
160
156
  <script setup lang="ts">
157
+ import { computed } from 'vue';
158
+ import { formatMessage } from '../../client/i18n/format.js';
159
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
161
160
  import type { ChecklistItem, PreviewMode, SelectedQuotaRow } from './types.js';
162
161
 
163
- defineProps<{
162
+ const props = defineProps<{
164
163
  previewMode: PreviewMode;
165
164
  catalogUrl: string;
166
165
  planKey: string;
@@ -181,4 +180,28 @@ defineProps<{
181
180
  defineEmits<{
182
181
  (e: 'update:previewMode', value: PreviewMode): void;
183
182
  }>();
183
+
184
+ const msg = useSaMessages('planEditor');
185
+
186
+ const eyebrow = computed(() =>
187
+ formatMessage(msg.value.catalogPreview.eyebrow, { version: props.version }),
188
+ );
189
+
190
+ const yearlyAlternative = computed(() =>
191
+ formatMessage(msg.value.catalogPreview.yearlyAlternative, {
192
+ price: props.formattedYearly,
193
+ savings: props.yearlySavingsLabel ?? '',
194
+ }),
195
+ );
196
+
197
+ const footerChangeNote = computed(() =>
198
+ formatMessage(msg.value.catalogPreview.footerChangeNote, { note: props.changeNote }),
199
+ );
200
+
201
+ const checklistCountLabel = computed(() =>
202
+ formatMessage(msg.value.catalogPreview.checklistCount, {
203
+ ok: props.checklistOkCount,
204
+ total: props.checklist.length,
205
+ }),
206
+ );
184
207
  </script>
@@ -2,8 +2,8 @@
2
2
  <section class="pve-col pve-pool">
3
3
  <div class="pve-col-header">
4
4
  <div>
5
- <div class="pve-col-title">Komponenten</div>
6
- <div class="pve-col-sub">Aus der Library in den Plan-Korb ziehen</div>
5
+ <div class="pve-col-title">{{ msg.componentPool.title }}</div>
6
+ <div class="pve-col-sub">{{ msg.componentPool.subtitle }}</div>
7
7
  </div>
8
8
  </div>
9
9
  <div class="pve-search">
@@ -22,7 +22,7 @@
22
22
  </span>
23
23
  <input
24
24
  :value="searchTerm"
25
- placeholder="In allen Komponenten suchen…"
25
+ :placeholder="msg.componentPool.searchPlaceholder"
26
26
  @input="updateSearchTerm"
27
27
  />
28
28
  <span class="pve-kbd">⌘ K</span>
@@ -101,7 +101,7 @@
101
101
  </div>
102
102
  </template>
103
103
  <div v-if="filteredFeatureGroups.length === 0" class="pve-empty">
104
- Keine Features im Discovery-Snapshot.
104
+ {{ msg.componentPool.emptyFeatures }}
105
105
  </div>
106
106
  </template>
107
107
 
@@ -162,7 +162,7 @@
162
162
  </div>
163
163
  </div>
164
164
  <div v-if="filteredQuotas.length === 0" class="pve-empty">
165
- Keine Quotas im Discovery-Snapshot.
165
+ {{ msg.componentPool.emptyQuotas }}
166
166
  </div>
167
167
  </template>
168
168
 
@@ -219,12 +219,12 @@
219
219
  <div class="pve-pool-card-meta">
220
220
  <code class="pve-pool-card-key">{{ b.bundleKey }}</code>
221
221
  <span class="pve-pool-card-dot">·</span>
222
- <span>{{ b.features.length }} Feature(s)</span>
222
+ <span>{{ featureCount(b.features.length) }}</span>
223
223
  </div>
224
224
  </div>
225
225
  </div>
226
226
  <div v-if="filteredBundles.length === 0" class="pve-empty">
227
- Keine published Bundles im Catalog.
227
+ {{ msg.componentPool.emptyBundles }}
228
228
  </div>
229
229
  </template>
230
230
  </div>
@@ -232,6 +232,8 @@
232
232
  </template>
233
233
 
234
234
  <script setup lang="ts">
235
+ import { formatMessage } from '../../client/i18n/format.js';
236
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
235
237
  import type {
236
238
  BundleEntry,
237
239
  DiscoveryQuota,
@@ -266,6 +268,12 @@ const emit = defineEmits<{
266
268
  (e: 'drag-end'): void;
267
269
  }>();
268
270
 
271
+ const msg = useSaMessages('planEditor');
272
+
273
+ function featureCount(count: number): string {
274
+ return formatMessage(msg.value.componentPool.featureCount, { count });
275
+ }
276
+
269
277
  function updateSearchTerm(event: Event): void {
270
278
  emit('update:searchTerm', (event.target as HTMLInputElement | null)?.value ?? '');
271
279
  }