@saasicat/ui-vue 0.19.0 → 0.22.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 (145) hide show
  1. package/dist/{catalog-JXdKdIqG.d.cts → catalog-DJcfm5N2.d.cts} +21 -7
  2. package/dist/{catalog-JXdKdIqG.d.ts → catalog-DJcfm5N2.d.ts} +21 -7
  3. package/dist/{chunk-DPTIOQXX.js → chunk-F7NRM6KI.js} +56 -28
  4. package/dist/{chunk-RVG6LQHL.js → chunk-LY52T4N4.js} +21 -2
  5. package/dist/{chunk-ORX3ZG47.js → chunk-XMMFN2WJ.js} +74 -3
  6. package/dist/client/index.cjs +134 -29
  7. package/dist/client/index.d.cts +53 -3
  8. package/dist/client/index.d.ts +53 -3
  9. package/dist/client/index.js +14 -2
  10. package/dist/index.cjs +321 -162
  11. package/dist/index.d.cts +30 -8
  12. package/dist/index.d.ts +30 -8
  13. package/dist/index.js +176 -133
  14. package/dist/quasar/index.cjs +78 -29
  15. package/dist/quasar/index.d.cts +2 -2
  16. package/dist/quasar/index.d.ts +2 -2
  17. package/dist/quasar/index.js +6 -2
  18. package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
  19. package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
  20. package/dist/{use-super-admin-i18n-BlqcD0N7.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
  21. package/dist/{use-super-admin-i18n-1nCOkvgi.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
  22. package/package.json +18 -8
  23. package/src/client/i18n/messages/bundles.ts +0 -2
  24. package/src/client/i18n/messages/common.ts +24 -0
  25. package/src/client/i18n/messages/discovery.ts +2 -2
  26. package/src/client/i18n/messages/marketing.ts +2 -4
  27. package/src/client/i18n/messages/plan-detail.ts +0 -4
  28. package/src/client/i18n/messages/plans.ts +20 -8
  29. package/src/client/i18n/messages/tenants.ts +0 -2
  30. package/src/client/index.ts +1 -0
  31. package/src/client/manifest-loader.ts +1 -1
  32. package/src/client/resolve-plans.ts +144 -0
  33. package/src/components/BundleVersionPublishDialog.vue +6 -6
  34. package/src/components/KvBlock.vue +4 -4
  35. package/src/components/MarketingPromotionsTab.vue +34 -48
  36. package/src/components/MfaPromptDialog.vue +8 -2
  37. package/src/components/TenantActionConfirmDialog.vue +4 -4
  38. package/src/components/VersionDiffPreview.vue +7 -7
  39. package/src/components/admin-page/AdminBody.vue +40 -0
  40. package/src/components/admin-page/AdminFilters.vue +12 -0
  41. package/src/components/admin-page/AdminHero.vue +49 -0
  42. package/src/components/admin-page/AdminKpi.vue +84 -0
  43. package/src/components/admin-page/AdminPage.vue +16 -0
  44. package/src/components/admin-page/AdminPaginator.vue +195 -0
  45. package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
  46. package/src/components/admin-page/AdminSection.vue +53 -0
  47. package/src/components/admin-page/AdminStatistics.vue +40 -0
  48. package/src/components/admin-page/AdminTable.vue +175 -0
  49. package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
  50. package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
  51. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
  52. package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
  53. package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
  54. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
  55. package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
  56. package/src/components/dialogs/PilotCreateDialog.vue +12 -12
  57. package/src/components/dialogs/PilotEditDialog.vue +7 -7
  58. package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
  59. package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
  60. package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
  61. package/src/components/plan/PlanGrid.vue +1 -1
  62. package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
  63. package/src/components/plan-detail/PlanAuditLog.vue +7 -8
  64. package/src/components/plan-detail/PlanDetail.vue +95 -292
  65. package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
  66. package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
  67. package/src/components/plan-detail/PlanVersionDiffPanel.vue +5 -8
  68. package/src/components/plan-detail/PlanVersionsPanel.vue +9 -11
  69. package/src/components/plan-list/PlanList.vue +206 -457
  70. package/src/components/plan-matrix/PlanMatrix.vue +66 -183
  71. package/src/components/plan-review/PlanReview.vue +191 -275
  72. package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
  73. package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
  74. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
  75. package/src/index.ts +1 -0
  76. package/src/pages-standard/AdminLayout.vue +38 -12
  77. package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
  78. package/src/pages-standard/AuditPage.vue +65 -109
  79. package/src/pages-standard/BundlesPage.vue +146 -179
  80. package/src/pages-standard/DashboardPage.vue +59 -154
  81. package/src/pages-standard/DiscoveryPage.vue +172 -227
  82. package/src/pages-standard/EmailHistoryPage.vue +118 -159
  83. package/src/pages-standard/MarketingCatalogPage.vue +399 -440
  84. package/src/pages-standard/PilotsPage.vue +69 -117
  85. package/src/pages-standard/PlansPage.vue +240 -112
  86. package/src/pages-standard/PlatformEmailPage.vue +56 -91
  87. package/src/pages-standard/PromoCodeDetailPage.vue +49 -74
  88. package/src/pages-standard/PromoCodesPage.vue +95 -128
  89. package/src/pages-standard/SubscriptionsPage.vue +19 -52
  90. package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
  91. package/src/pages-standard/SuperAdminSetupWizard.vue +22 -18
  92. package/src/pages-standard/TenantDetailPage.vue +100 -191
  93. package/src/pages-standard/TenantsPage.vue +190 -295
  94. package/src/pages-standard/UsersPage.vue +89 -157
  95. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
  96. package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
  97. package/src/pages-standard/bundles-page/BundlesToolbar.vue +63 -0
  98. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
  99. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
  100. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
  101. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
  102. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
  103. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +111 -73
  104. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +43 -62
  105. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +53 -35
  106. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +7 -7
  107. package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
  108. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
  109. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
  110. package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
  111. package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
  112. package/src/pages-standard/plans-page/PlansPageToast.vue +6 -6
  113. package/src/pages-standard/sa-theme.css +552 -86
  114. package/src/pages-standard/tenant-detail/TenantMasterData.vue +28 -0
  115. package/src/pages-standard/tenant-detail/TenantUsage.vue +31 -0
  116. package/src/pages-standard/tenant-detail/TenantUsers.vue +15 -0
  117. package/src/pages-standard/tenant-detail/types.ts +38 -0
  118. package/src/pages-standard/tenants/StatusPill.vue +2 -2
  119. package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
  120. package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
  121. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  122. package/src/pages-tenant/TenantPlanSection.vue +12 -20
  123. package/src/quasar/create-super-admin-app.ts +4 -0
  124. package/src/vue/entitlement-provider.ts +0 -1
  125. package/src/vue/feature-router-guard.ts +0 -1
  126. package/src/vue/super-admin-context.ts +106 -1
  127. package/src/vue/use-api-list.ts +2 -1
  128. package/src/vue/use-mfa-prompt.ts +72 -0
  129. package/src/vue/use-pagination.ts +63 -0
  130. package/src/vue/use-plans.ts +1 -2
  131. package/src/vue/use-sign-out.ts +90 -0
  132. package/src/vue/use-tenant-billing.ts +1 -1
  133. package/src/vue/use-tenant-manifest.ts +0 -1
  134. package/src/components/KpiCard.vue +0 -53
  135. package/src/components/plan-cockpit/PlanCockpit.vue +0 -1012
  136. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
  137. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -100
  138. package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
  139. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
  140. package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
  141. package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -251
  142. package/src/components/plan-cockpit/types.ts +0 -72
  143. package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
  144. package/src/pages-standard/bundles-page/BundlesHeader.vue +0 -68
  145. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +0 -34
@@ -1,259 +1,239 @@
1
1
  <template>
2
2
  <div class="pr">
3
- <!-- Header -->
4
- <div class="pr-head">
5
- <div class="pr-head-text">
6
- <h2 class="pr-title">{{ msg.review.title }}</h2>
7
- <p class="pr-sub">
8
- {{ msg.review.subtitleLead }}
9
- <code class="pr-mono">{{ plan.planKey }}@v{{ version.version }}</code>
10
- {{ msg.review.subtitleTail }}
11
- </p>
12
- </div>
13
- <div class="pr-head-actions">
3
+ <AdminHero :title="msg.review.title">
4
+ <template #subtitle>
5
+ {{ msg.review.subtitleLead }}
6
+ <code class="pr-mono">{{ plan.planKey }}@v{{ version.version }}</code>
7
+ {{ msg.review.subtitleTail }}
8
+ </template>
9
+ <template #actions>
14
10
  <button
15
- class="pr-btn"
11
+ class="sa-btn"
16
12
  type="button"
17
13
  :disabled="publishing || saving"
18
14
  @click="$emit('back')"
19
15
  >
20
- <span class="pr-ico pr-ico--back" aria-hidden="true">
21
- <svg
22
- width="14"
23
- height="14"
24
- viewBox="0 0 24 24"
25
- fill="none"
26
- stroke="currentColor"
27
- stroke-width="2"
28
- >
29
- <path d="M5 12h14M13 5l7 7-7 7" />
30
- </svg>
31
- </span>
16
+ <q-icon name="arrow_back" size="16px" />
32
17
  <span>{{ common.back }}</span>
33
18
  </button>
34
19
  <button
35
- class="pr-btn"
20
+ class="sa-btn"
36
21
  type="button"
37
22
  :disabled="publishing || saving"
38
23
  @click="$emit('saveAndExit')"
39
24
  >
40
- {{ saving ? msg.review.saving : msg.review.saveAsDraft }}
25
+ <span>{{ saving ? msg.review.saving : msg.review.saveAsDraft }}</span>
41
26
  </button>
42
27
  <button
43
- class="pr-btn pr-btn--primary"
28
+ class="sa-btn sa-btn--primary"
44
29
  type="button"
45
30
  :disabled="!canPublish || publishing || saving"
46
31
  :title="canPublish ? undefined : msg.review.publishBlocked"
47
32
  @click="onPublish"
48
33
  >
49
- <svg
50
- width="14"
51
- height="14"
52
- viewBox="0 0 24 24"
53
- fill="none"
54
- stroke="currentColor"
55
- stroke-width="2"
56
- >
57
- <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
58
- </svg>
34
+ <q-icon name="bolt" size="16px" />
59
35
  <span>{{ publishing ? msg.review.publishing : publishLabel }}</span>
60
36
  </button>
61
- </div>
62
- </div>
63
-
64
- <div v-if="publishError" class="pr-banner">{{ publishError }}</div>
65
-
66
- <div class="pr-grid">
67
- <!-- Left column -->
68
- <div class="pr-col">
69
- <div class="pr-card">
70
- <h3 class="pr-card-title">{{ msg.review.cardMasterData }}</h3>
71
- <div class="pr-row">
72
- <div class="pr-label">{{ msg.review.labelPlanKey }}</div>
73
- <div class="pr-val pr-mono">{{ plan.planKey }}</div>
74
- </div>
75
- <div class="pr-row">
76
- <div class="pr-label">{{ msg.review.labelDisplayName }}</div>
77
- <div class="pr-val">{{ plan.label }}</div>
78
- </div>
79
- <div class="pr-row">
80
- <div class="pr-label">{{ common.description }}</div>
81
- <div class="pr-val">
82
- <template v-if="plan.description">{{ plan.description }}</template>
83
- <i v-else class="pr-missing">{{ msg.review.missing }}</i>
37
+ </template>
38
+ </AdminHero>
39
+
40
+ <AdminBody>
41
+ <div v-if="publishError" class="pr-banner">{{ publishError }}</div>
42
+
43
+ <div class="pr-grid">
44
+ <!-- Left column -->
45
+ <div class="pr-col">
46
+ <div class="pr-card">
47
+ <h3 class="pr-card-title">{{ msg.review.cardMasterData }}</h3>
48
+ <div class="pr-row">
49
+ <div class="pr-label">{{ msg.review.labelPlanKey }}</div>
50
+ <div class="pr-val pr-mono">{{ plan.planKey }}</div>
84
51
  </div>
85
- </div>
86
- </div>
87
-
88
- <div class="pr-card">
89
- <h3 class="pr-card-title">{{ pricingCardTitle }}</h3>
90
- <div class="pr-row">
91
- <div class="pr-label">{{ msg.review.labelValidFrom }}</div>
92
- <div class="pr-val">
93
- <template v-if="version.validFrom">{{
94
- version.validFrom.slice(0, 10)
95
- }}</template>
96
- <i v-else class="pr-missing">{{
97
- msg.review.missingRequiredOnPublish
98
- }}</i>
99
- </div>
100
- </div>
101
- <div class="pr-row">
102
- <div class="pr-label">{{ msg.review.labelValidUntil }}</div>
103
- <div class="pr-val">
104
- <template v-if="version.validUntil">{{
105
- version.validUntil.slice(0, 10)
106
- }}</template>
107
- <span v-else class="pr-inf">{{ msg.review.unlimited }}</span>
52
+ <div class="pr-row">
53
+ <div class="pr-label">{{ msg.review.labelDisplayName }}</div>
54
+ <div class="pr-val">{{ plan.label }}</div>
108
55
  </div>
109
- </div>
110
- <div class="pr-row">
111
- <div class="pr-label">{{ msg.review.labelMonthlyPrice }}</div>
112
- <div class="pr-val">
113
- <template v-if="hasMonthly">{{
114
- formatMoney(version.monthlyNet)
115
- }}</template>
116
- <i v-else class="pr-missing">{{ msg.review.missing }}</i>
117
- </div>
118
- </div>
119
- <div class="pr-row">
120
- <div class="pr-label">{{ msg.review.labelYearlyPrice }}</div>
121
- <div class="pr-val">{{ formatMoney(version.yearlyNet) }}</div>
122
- </div>
123
- <div class="pr-row">
124
- <div class="pr-label">{{ msg.review.labelPublicCatalog }}</div>
125
- <div class="pr-val">
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>
56
+ <div class="pr-row">
57
+ <div class="pr-label">{{ common.description }}</div>
58
+ <div class="pr-val">
59
+ <template v-if="plan.description">{{ plan.description }}</template>
60
+ <i v-else class="pr-missing">{{ msg.review.missing }}</i>
61
+ </div>
132
62
  </div>
133
63
  </div>
134
- </div>
135
64
 
136
- <div class="pr-card">
137
- <h3 class="pr-card-title">{{ componentsCardTitle }}</h3>
138
- <div class="pr-components">
139
- <div class="pr-comp-col">
140
- <div class="pr-comp-head pr-comp-head--quota">
141
- {{ quotaSectionTitle }}
142
- </div>
143
- <div v-for="q in quotaList" :key="q.key" class="pr-comp-item">
144
- <span>{{ q.label }}</span>
145
- <b>{{ q.value }}{{ q.unit ? ' ' + q.unit : '' }}</b>
146
- </div>
147
- <div v-if="quotaList.length === 0" class="pr-comp-empty">
148
- {{ msg.review.componentEmpty }}
65
+ <div class="pr-card">
66
+ <h3 class="pr-card-title">{{ pricingCardTitle }}</h3>
67
+ <div class="pr-row">
68
+ <div class="pr-label">{{ msg.review.labelValidFrom }}</div>
69
+ <div class="pr-val">
70
+ <template v-if="version.validFrom">{{
71
+ version.validFrom.slice(0, 10)
72
+ }}</template>
73
+ <i v-else class="pr-missing">{{
74
+ msg.review.missingRequiredOnPublish
75
+ }}</i>
149
76
  </div>
150
77
  </div>
151
- <div class="pr-comp-col">
152
- <div class="pr-comp-head pr-comp-head--feature">
153
- {{ featureSectionTitle }}
78
+ <div class="pr-row">
79
+ <div class="pr-label">{{ msg.review.labelValidUntil }}</div>
80
+ <div class="pr-val">
81
+ <template v-if="version.validUntil">{{
82
+ version.validUntil.slice(0, 10)
83
+ }}</template>
84
+ <span v-else class="pr-inf">{{ msg.review.unlimited }}</span>
154
85
  </div>
155
- <div v-for="f in featureList" :key="f.key" class="pr-comp-item">
156
- <span>{{ f.label }}</span>
86
+ </div>
87
+ <div class="pr-row">
88
+ <div class="pr-label">{{ msg.review.labelMonthlyPrice }}</div>
89
+ <div class="pr-val">
90
+ <template v-if="hasMonthly">{{
91
+ formatMoney(version.monthlyNet)
92
+ }}</template>
93
+ <i v-else class="pr-missing">{{ msg.review.missing }}</i>
157
94
  </div>
158
- <div v-if="featureList.length === 0" class="pr-comp-empty">
159
- {{ msg.review.componentEmpty }}
95
+ </div>
96
+ <div class="pr-row">
97
+ <div class="pr-label">{{ msg.review.labelYearlyPrice }}</div>
98
+ <div class="pr-val">{{ formatMoney(version.yearlyNet) }}</div>
99
+ </div>
100
+ <div class="pr-row">
101
+ <div class="pr-label">{{ msg.review.labelPublicCatalog }}</div>
102
+ <div class="pr-val">
103
+ <span v-if="version.marketed" class="pr-chip pr-chip--live">{{
104
+ msg.review.visibleInCatalog
105
+ }}</span>
106
+ <span v-else class="pr-chip pr-chip--muted">{{
107
+ msg.review.hiddenFromCatalog
108
+ }}</span>
160
109
  </div>
161
110
  </div>
162
- <div class="pr-comp-col">
163
- <div class="pr-comp-head pr-comp-head--bundle">
164
- {{ bundleSectionTitle }}
111
+ </div>
112
+
113
+ <div class="pr-card">
114
+ <h3 class="pr-card-title">{{ componentsCardTitle }}</h3>
115
+ <div class="pr-components">
116
+ <div class="pr-comp-col">
117
+ <div class="pr-comp-head pr-comp-head--quota">
118
+ {{ quotaSectionTitle }}
119
+ </div>
120
+ <div v-for="q in quotaList" :key="q.key" class="pr-comp-item">
121
+ <span>{{ q.label }}</span>
122
+ <b>{{ q.value }}{{ q.unit ? ' ' + q.unit : '' }}</b>
123
+ </div>
124
+ <div v-if="quotaList.length === 0" class="pr-comp-empty">
125
+ {{ msg.review.componentEmpty }}
126
+ </div>
165
127
  </div>
166
- <div v-for="b in bundleList" :key="b.key" class="pr-comp-item">
167
- <span>{{ b.label }}</span>
128
+ <div class="pr-comp-col">
129
+ <div class="pr-comp-head pr-comp-head--feature">
130
+ {{ featureSectionTitle }}
131
+ </div>
132
+ <div v-for="f in featureList" :key="f.key" class="pr-comp-item">
133
+ <span>{{ f.label }}</span>
134
+ </div>
135
+ <div v-if="featureList.length === 0" class="pr-comp-empty">
136
+ {{ msg.review.componentEmpty }}
137
+ </div>
168
138
  </div>
169
- <div v-if="bundleList.length === 0" class="pr-comp-empty">
170
- {{ msg.review.componentEmpty }}
139
+ <div class="pr-comp-col">
140
+ <div class="pr-comp-head pr-comp-head--bundle">
141
+ {{ bundleSectionTitle }}
142
+ </div>
143
+ <div v-for="b in bundleList" :key="b.key" class="pr-comp-item">
144
+ <span>{{ b.label }}</span>
145
+ </div>
146
+ <div v-if="bundleList.length === 0" class="pr-comp-empty">
147
+ {{ msg.review.componentEmpty }}
148
+ </div>
171
149
  </div>
172
150
  </div>
173
151
  </div>
174
152
  </div>
175
- </div>
176
153
 
177
- <!-- Right column -->
178
- <div class="pr-col">
179
- <div class="pr-card">
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>
184
- <div v-if="version.changeNote" class="pr-note">{{ version.changeNote }}</div>
185
- <div v-else class="pr-note pr-note--missing">
186
- {{ msg.review.changeNoteMissing }}
154
+ <!-- Right column -->
155
+ <div class="pr-col">
156
+ <div class="pr-card">
157
+ <h3 class="pr-card-title">
158
+ {{ msg.review.cardChangeNote }} <span class="pr-req">*</span>
159
+ </h3>
160
+ <p class="pr-card-hint">{{ msg.review.changeNoteHint }}</p>
161
+ <div v-if="version.changeNote" class="pr-note">
162
+ {{ version.changeNote }}
163
+ </div>
164
+ <div v-else class="pr-note pr-note--missing">
165
+ {{ msg.review.changeNoteMissing }}
166
+ </div>
187
167
  </div>
188
- </div>
189
168
 
190
- <div class="pr-card">
191
- <h3 class="pr-card-title">{{ msg.review.cardChecklist }}</h3>
192
- <div class="pr-checks">
193
- <div
194
- v-for="c in checks"
195
- :key="c.id"
196
- class="pr-check"
197
- :class="c.ok ? 'pr-check--ok' : 'pr-check--warn'"
198
- >
199
- <span class="pr-check-mark">
200
- <svg
201
- v-if="c.ok"
202
- width="11"
203
- height="11"
204
- viewBox="0 0 24 24"
205
- fill="none"
206
- stroke="currentColor"
207
- stroke-width="3.5"
208
- >
209
- <path d="M5 13l4 4L19 7" />
210
- </svg>
211
- <svg
212
- v-else
213
- width="11"
214
- height="11"
215
- viewBox="0 0 24 24"
216
- fill="none"
217
- stroke="currentColor"
218
- stroke-width="2.5"
219
- >
220
- <path
221
- d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0zM12 9v4M12 17h.01"
222
- />
223
- </svg>
224
- </span>
225
- <span>{{ c.label }}</span>
169
+ <div class="pr-card">
170
+ <h3 class="pr-card-title">{{ msg.review.cardChecklist }}</h3>
171
+ <div class="pr-checks">
172
+ <div
173
+ v-for="c in checks"
174
+ :key="c.id"
175
+ class="pr-check"
176
+ :class="c.ok ? 'pr-check--ok' : 'pr-check--warn'"
177
+ >
178
+ <span class="pr-check-mark">
179
+ <svg
180
+ v-if="c.ok"
181
+ width="11"
182
+ height="11"
183
+ viewBox="0 0 24 24"
184
+ fill="none"
185
+ stroke="currentColor"
186
+ stroke-width="3.5"
187
+ >
188
+ <path d="M5 13l4 4L19 7" />
189
+ </svg>
190
+ <svg
191
+ v-else
192
+ width="11"
193
+ height="11"
194
+ viewBox="0 0 24 24"
195
+ fill="none"
196
+ stroke="currentColor"
197
+ stroke-width="2.5"
198
+ >
199
+ <path
200
+ d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0zM12 9v4M12 17h.01"
201
+ />
202
+ </svg>
203
+ </span>
204
+ <span>{{ c.label }}</span>
205
+ </div>
226
206
  </div>
227
207
  </div>
228
- </div>
229
208
 
230
- <div v-if="predecessor" class="pr-card pr-card--regress">
231
- <h3 class="pr-card-title">{{ msg.review.cardRegression }}</h3>
232
- <p class="pr-card-hint">{{ msg.review.regressionHint }}</p>
233
- <label class="pr-toggle">
234
- <input v-model="forceRegressive" type="checkbox" />
235
- <span>{{ msg.review.forceRegressive }}</span>
236
- </label>
237
- </div>
209
+ <div v-if="predecessor" class="pr-card pr-card--regress">
210
+ <h3 class="pr-card-title">{{ msg.review.cardRegression }}</h3>
211
+ <p class="pr-card-hint">{{ msg.review.regressionHint }}</p>
212
+ <label class="pr-toggle">
213
+ <input v-model="forceRegressive" type="checkbox" />
214
+ <span>{{ msg.review.forceRegressive }}</span>
215
+ </label>
216
+ </div>
238
217
 
239
- <div class="pr-card">
240
- <h3 class="pr-card-title">{{ msg.review.cardZeroPrice }}</h3>
241
- <p class="pr-card-hint">{{ msg.review.zeroPriceHint }}</p>
242
- <label class="pr-toggle">
243
- <input v-model="allowZeroPrice" type="checkbox" />
244
- <span>{{ msg.review.allowZeroPrice }}</span>
245
- </label>
246
- </div>
218
+ <div class="pr-card">
219
+ <h3 class="pr-card-title">{{ msg.review.cardZeroPrice }}</h3>
220
+ <p class="pr-card-hint">{{ msg.review.zeroPriceHint }}</p>
221
+ <label class="pr-toggle">
222
+ <input v-model="allowZeroPrice" type="checkbox" />
223
+ <span>{{ msg.review.allowZeroPrice }}</span>
224
+ </label>
225
+ </div>
247
226
 
248
- <div class="pr-card pr-card--impact">
249
- <h3 class="pr-card-title">{{ msg.review.cardTenantImpact }}</h3>
250
- <div class="pr-impact">
251
- <div class="pr-impact-num">{{ tenantImpactCount }}</div>
252
- <div class="pr-impact-text">{{ impactText }}</div>
227
+ <div class="pr-card pr-card--impact">
228
+ <h3 class="pr-card-title">{{ msg.review.cardTenantImpact }}</h3>
229
+ <div class="pr-impact">
230
+ <div class="pr-impact-num">{{ tenantImpactCount }}</div>
231
+ <div class="pr-impact-text">{{ impactText }}</div>
232
+ </div>
253
233
  </div>
254
234
  </div>
255
235
  </div>
256
- </div>
236
+ </AdminBody>
257
237
  </div>
258
238
  </template>
259
239
 
@@ -263,8 +243,12 @@ import type { PlanRow, PlanVersionRow } from '@saasicat/types';
263
243
  import { formatMessage } from '../../client/i18n/format.js';
264
244
  import { formatCurrency } from '../../client/i18n/currency.js';
265
245
  import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
246
+ // This view replaces the page hero while it is open: its actions hang on the
247
+ // publish checklist and the force flags below, which live here.
248
+ import AdminBody from '../admin-page/AdminBody.vue';
249
+ import AdminHero from '../admin-page/AdminHero.vue';
266
250
 
267
- // PlanReview — step 3 of the plan wizard (SPEC_V2 §6, plan simulation
251
+ // PlanReview — step 3 of the plan wizard (plan simulation
268
252
  // "Review & Publish"). Shows the saved draft read-only, checks the
269
253
  // publish checklist and triggers publish. The draft was already persisted
270
254
  // in the editor (step 2); review mutates nothing except publish.
@@ -401,7 +385,7 @@ const hasMonthly = computed(() => {
401
385
  });
402
386
 
403
387
  // "Gültig ab" must lie strictly after the "Gültig ab" of the predecessor
404
- // version (SPEC_V2 §4.2.1) — otherwise the publish endpoint rejects.
388
+ // version — otherwise the publish endpoint rejects.
405
389
  const validFromAfterPredecessor = computed(() => {
406
390
  const prev = props.predecessor;
407
391
  if (!prev?.validFrom || !props.version.validFrom) return true;
@@ -468,82 +452,14 @@ function onPublish(): void {
468
452
 
469
453
  <style scoped>
470
454
  .pr {
471
- padding: 22px 26px;
472
- background: var(--sa-bg-app);
473
455
  min-height: 100%;
474
456
  box-sizing: border-box;
475
- font-family: var(--sa-font-body);
476
- color: var(--sa-body);
477
- }
478
- .pr * {
479
- box-sizing: border-box;
480
457
  }
481
458
  .pr-mono {
482
459
  font-family: var(--sa-font-mono);
483
460
  font-weight: 600;
484
461
  }
485
462
 
486
- .pr-head {
487
- display: flex;
488
- align-items: flex-end;
489
- gap: 20px;
490
- margin-bottom: 16px;
491
- }
492
- .pr-head-text {
493
- flex: 1;
494
- min-width: 0;
495
- }
496
- .pr-title {
497
- font: 700 22px/1.2 var(--sa-font-head);
498
- letter-spacing: -0.02em;
499
- color: var(--sa-heading);
500
- margin: 0;
501
- }
502
- .pr-sub {
503
- font-size: 12.5px;
504
- color: var(--sa-muted);
505
- margin: 4px 0 0;
506
- }
507
- .pr-head-actions {
508
- display: flex;
509
- gap: 8px;
510
- }
511
-
512
- .pr-btn {
513
- display: inline-flex;
514
- align-items: center;
515
- gap: 7px;
516
- padding: 8px 14px;
517
- border-radius: 7px;
518
- font: 500 13px var(--sa-font-body);
519
- cursor: pointer;
520
- border: 1px solid var(--sa-border);
521
- background: var(--sa-bg-surface);
522
- color: var(--sa-heading);
523
- transition:
524
- background 0.12s,
525
- border-color 0.12s;
526
- }
527
- .pr-btn:hover:not(:disabled) {
528
- background: var(--sa-border-soft);
529
- }
530
- .pr-btn:disabled {
531
- cursor: not-allowed;
532
- opacity: 0.5;
533
- }
534
- .pr-btn--primary {
535
- background: var(--sa-primary);
536
- border-color: var(--sa-primary);
537
- color: #fff;
538
- }
539
- .pr-btn--primary:hover:not(:disabled) {
540
- filter: brightness(0.94);
541
- }
542
- .pr-ico--back {
543
- display: inline-flex;
544
- transform: rotate(180deg);
545
- }
546
-
547
463
  .pr-banner {
548
464
  background: var(--sa-warning-soft);
549
465
  border: 1px solid var(--sa-amber-border);
@@ -8,17 +8,14 @@
8
8
  <div class="pve-preview-toggle">
9
9
  <button
10
10
  type="button"
11
- :class="[
12
- 'pve-btn pve-btn--sm',
13
- { 'pve-btn--ghost': previewMode !== 'desktop' },
14
- ]"
11
+ :class="['sa-btn sa-btn--sm', { 'sa-btn--ghost': previewMode !== 'desktop' }]"
15
12
  @click="$emit('update:previewMode', 'desktop')"
16
13
  >
17
14
  {{ msg.catalogPreview.desktop }}
18
15
  </button>
19
16
  <button
20
17
  type="button"
21
- :class="['pve-btn pve-btn--sm', { 'pve-btn--ghost': previewMode !== 'mobile' }]"
18
+ :class="['sa-btn sa-btn--sm', { 'sa-btn--ghost': previewMode !== 'mobile' }]"
22
19
  @click="$emit('update:previewMode', 'mobile')"
23
20
  >
24
21
  {{ msg.catalogPreview.mobile }}
@@ -147,6 +144,11 @@
147
144
  <path d="M12 9v4M12 17h.01" />
148
145
  </svg>
149
146
  </span>
147
+ <!-- Checklist labels come from the i18n catalog, where one
148
+ entry (`checklistTenantImpact`) emphasises the affected
149
+ tenant count with a bold span. Both interpolated values are
150
+ numbers the platform computed, never request data. -->
151
+ <!-- eslint-disable-next-line vue/no-v-html -->
150
152
  <span v-html="item.label" />
151
153
  </div>
152
154
  </div>