@saasicat/ui-vue 0.2.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 (193) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +47 -0
  3. package/dist/index.cjs +3776 -0
  4. package/dist/index.d.cts +2395 -0
  5. package/dist/index.d.ts +2395 -0
  6. package/dist/index.js +3659 -0
  7. package/dist/testing-e2e/admin-pages-suite.cjs +134 -0
  8. package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
  9. package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
  10. package/dist/testing-e2e/admin-pages-suite.js +109 -0
  11. package/package.json +87 -0
  12. package/src/action-registry.ts +110 -0
  13. package/src/batch-column-fetcher.ts +152 -0
  14. package/src/boot-loader.ts +60 -0
  15. package/src/components/BundleVersionPublishDialog.vue +359 -0
  16. package/src/components/BusinessTypeVersionEditorDialog.vue +417 -0
  17. package/src/components/BusinessTypeVersionPublishDialog.vue +258 -0
  18. package/src/components/FeatureGate.vue +56 -0
  19. package/src/components/KpiCard.vue +53 -0
  20. package/src/components/KvBlock.vue +36 -0
  21. package/src/components/MarketingPromotionsTab.vue +861 -0
  22. package/src/components/MfaPromptDialog.vue +104 -0
  23. package/src/components/TenantActionConfirmDialog.vue +252 -0
  24. package/src/components/VersionDiffPreview.vue +150 -0
  25. package/src/components/bundle-editor/BundleCreatePanel.vue +681 -0
  26. package/src/components/bundle-editor/BundleFeaturesEditor.vue +215 -0
  27. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +316 -0
  28. package/src/components/bundle-editor/BundleQuotasEditor.vue +219 -0
  29. package/src/components/bundle-editor/BundleStatusBanner.vue +189 -0
  30. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +659 -0
  31. package/src/components/bundle-editor/BundleVersionStrip.vue +207 -0
  32. package/src/components/bundle-editor/bundle-version-status.ts +164 -0
  33. package/src/components/bundle-editor/catalog-i18n.ts +85 -0
  34. package/src/components/dialogs/PilotCreateDialog.vue +780 -0
  35. package/src/components/dialogs/PilotEditDialog.vue +548 -0
  36. package/src/components/dialogs/PromoCodeCreateDialog.vue +737 -0
  37. package/src/components/dialogs/PromoCodeEditDialog.vue +859 -0
  38. package/src/components/dialogs/types.ts +126 -0
  39. package/src/components/plan/PlanCycleToggle.vue +99 -0
  40. package/src/components/plan/PlanGrid.vue +211 -0
  41. package/src/components/plan/PriceSummary.vue +293 -0
  42. package/src/components/plan/PromoCodeInput.vue +192 -0
  43. package/src/components/plan/PublicBundleGrid.vue +287 -0
  44. package/src/components/plan-cockpit/PlanCockpit.vue +993 -0
  45. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +65 -0
  46. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +91 -0
  47. package/src/components/plan-cockpit/PlanCockpitHeader.vue +98 -0
  48. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +99 -0
  49. package/src/components/plan-cockpit/PlanCockpitKpis.vue +57 -0
  50. package/src/components/plan-cockpit/PlanCockpitVersions.vue +226 -0
  51. package/src/components/plan-cockpit/types.ts +72 -0
  52. package/src/components/plan-create-dialog/PlanCreateDialog.vue +508 -0
  53. package/src/components/plan-detail/PlanAuditLog.vue +59 -0
  54. package/src/components/plan-detail/PlanDetail.vue +1195 -0
  55. package/src/components/plan-detail/PlanDetailHeader.vue +185 -0
  56. package/src/components/plan-detail/PlanDetailKpis.vue +57 -0
  57. package/src/components/plan-detail/PlanTerminateDialog.vue +76 -0
  58. package/src/components/plan-detail/PlanVersionDiffPanel.vue +149 -0
  59. package/src/components/plan-detail/PlanVersionsPanel.vue +255 -0
  60. package/src/components/plan-detail/types.ts +59 -0
  61. package/src/components/plan-list/PlanList.vue +1067 -0
  62. package/src/components/plan-matrix/PlanMatrix.vue +1113 -0
  63. package/src/components/plan-review/PlanReview.vue +742 -0
  64. package/src/components/plan-version-editor/PlanCatalogPreview.vue +184 -0
  65. package/src/components/plan-version-editor/PlanComponentPool.vue +272 -0
  66. package/src/components/plan-version-editor/PlanVersionBasket.vue +281 -0
  67. package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +84 -0
  68. package/src/components/plan-version-editor/PlanVersionEditor.vue +1728 -0
  69. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +94 -0
  70. package/src/components/plan-version-editor/types.ts +96 -0
  71. package/src/components/wizard-stepper/WizardStepper.vue +107 -0
  72. package/src/create-admin-routes.ts +68 -0
  73. package/src/create-super-admin-app.ts +345 -0
  74. package/src/entitlement-provider.ts +36 -0
  75. package/src/feature-router-guard.ts +77 -0
  76. package/src/http-json.ts +62 -0
  77. package/src/index.ts +98 -0
  78. package/src/manifest-loader.ts +139 -0
  79. package/src/manifest-store-factory.ts +114 -0
  80. package/src/nav-builder.ts +274 -0
  81. package/src/pages-standard/AdminLayout.vue +411 -0
  82. package/src/pages-standard/AdminManifestErrorPage.vue +79 -0
  83. package/src/pages-standard/AuditPage.vue +223 -0
  84. package/src/pages-standard/BundlesPage.vue +910 -0
  85. package/src/pages-standard/BusinessTypesPage.vue +662 -0
  86. package/src/pages-standard/DashboardPage.vue +595 -0
  87. package/src/pages-standard/DiscoveryPage.vue +663 -0
  88. package/src/pages-standard/EmailHistoryPage.vue +576 -0
  89. package/src/pages-standard/MarketingCatalogPage.vue +1771 -0
  90. package/src/pages-standard/PilotsPage.vue +577 -0
  91. package/src/pages-standard/PlanVersionsPage.vue +216 -0
  92. package/src/pages-standard/PlansPage.vue +1222 -0
  93. package/src/pages-standard/PlatformEmailPage.vue +434 -0
  94. package/src/pages-standard/PromoCodeDetailPage.vue +331 -0
  95. package/src/pages-standard/PromoCodesPage.vue +589 -0
  96. package/src/pages-standard/SubscriptionsPage.vue +159 -0
  97. package/src/pages-standard/SuperAdminLoginPage.vue +299 -0
  98. package/src/pages-standard/SuperAdminSetupWizard.vue +405 -0
  99. package/src/pages-standard/TenantDetailPage.vue +432 -0
  100. package/src/pages-standard/TenantsPage.vue +732 -0
  101. package/src/pages-standard/UsersPage.vue +504 -0
  102. package/src/pages-standard/bundles-page/BundleAccordionList.vue +116 -0
  103. package/src/pages-standard/bundles-page/BundleDetailPanel.vue +193 -0
  104. package/src/pages-standard/bundles-page/BundlesFilterBar.vue +48 -0
  105. package/src/pages-standard/bundles-page/BundlesHeader.vue +67 -0
  106. package/src/pages-standard/bundles-page/BundlesKpis.vue +39 -0
  107. package/src/pages-standard/bundles-page/types.ts +15 -0
  108. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +246 -0
  109. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +171 -0
  110. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +410 -0
  111. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +31 -0
  112. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +47 -0
  113. package/src/pages-standard/discovery-page/DiscoveryMetaBanner.vue +26 -0
  114. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +247 -0
  115. package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +73 -0
  116. package/src/pages-standard/discovery-page/discovery-ui.ts +166 -0
  117. package/src/pages-standard/email-history.types.ts +48 -0
  118. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +448 -0
  119. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +93 -0
  120. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +138 -0
  121. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +58 -0
  122. package/src/pages-standard/marketing-catalog/types.ts +43 -0
  123. package/src/pages-standard/plan-versions/PlanDiffCard.vue +132 -0
  124. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +293 -0
  125. package/src/pages-standard/plan-versions/PlanVersionStatusBadge.vue +48 -0
  126. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +307 -0
  127. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +269 -0
  128. package/src/pages-standard/plan-versions/PlanVersionsKpi.vue +65 -0
  129. package/src/pages-standard/plan-versions/PlanVersionsList.vue +292 -0
  130. package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +261 -0
  131. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +300 -0
  132. package/src/pages-standard/plan-versions/VersionDiffPreview.vue +167 -0
  133. package/src/pages-standard/plan-versions/format.ts +53 -0
  134. package/src/pages-standard/plan-versions/types.ts +33 -0
  135. package/src/pages-standard/plans-page/PlanArchiveDialog.vue +50 -0
  136. package/src/pages-standard/plans-page/PlanBundleOverview.vue +147 -0
  137. package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +46 -0
  138. package/src/pages-standard/plans-page/PlanPublishDialog.vue +106 -0
  139. package/src/pages-standard/plans-page/PlansPageToast.vue +69 -0
  140. package/src/pages-standard/plans-page/types.ts +18 -0
  141. package/src/pages-standard/platform-email.types.ts +41 -0
  142. package/src/pages-standard/sa-theme.css +223 -0
  143. package/src/pages-standard/tenants/StatusPill.vue +62 -0
  144. package/src/pages-standard/tenants/format.ts +38 -0
  145. package/src/pages-tenant/LimitsRow.vue +67 -0
  146. package/src/pages-tenant/MySubscriptionBundlesPage.vue +604 -0
  147. package/src/pages-tenant/OnboardingConfigurator.vue +417 -0
  148. package/src/pages-tenant/PackageSnapshotPanel.vue +241 -0
  149. package/src/pages-tenant/PendingVersionBanner.vue +126 -0
  150. package/src/pages-tenant/PlanChangeWizard.vue +630 -0
  151. package/src/pages-tenant/TenantPlanSection.vue +729 -0
  152. package/src/pages-tenant/UsageBar.vue +107 -0
  153. package/src/pages-tenant/default-i18n.ts +280 -0
  154. package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +304 -0
  155. package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +323 -0
  156. package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +122 -0
  157. package/src/pages-tenant/tenant-plan-section/TenantPlanCardHeader.vue +67 -0
  158. package/src/pages-tenant/tenant-plan-section/TenantUsageGrid.vue +31 -0
  159. package/src/plan-versions-catalog.ts +368 -0
  160. package/src/platform-loaders.ts +68 -0
  161. package/src/project-page-host.ts +108 -0
  162. package/src/testing-e2e/admin-pages-suite.ts +187 -0
  163. package/src/types.ts +58 -0
  164. package/src/use-actions.ts +25 -0
  165. package/src/use-api-list.ts +151 -0
  166. package/src/use-audit-entries.ts +48 -0
  167. package/src/use-batch-columns.ts +59 -0
  168. package/src/use-bulk-publish.ts +150 -0
  169. package/src/use-bundle-versions-map.ts +112 -0
  170. package/src/use-bundles.ts +297 -0
  171. package/src/use-business-types.ts +269 -0
  172. package/src/use-catalog-entries.ts +243 -0
  173. package/src/use-discovery.ts +158 -0
  174. package/src/use-entitlement.ts +87 -0
  175. package/src/use-live-plan-versions.ts +126 -0
  176. package/src/use-manifest.ts +57 -0
  177. package/src/use-marketing-projections.ts +160 -0
  178. package/src/use-nav.ts +33 -0
  179. package/src/use-plan-editor.ts +162 -0
  180. package/src/use-plan-versions.ts +83 -0
  181. package/src/use-plans.ts +375 -0
  182. package/src/use-platform-tenant-actions.ts +286 -0
  183. package/src/use-promotions.ts +126 -0
  184. package/src/use-public-boot.ts +40 -0
  185. package/src/use-subscription-draft.ts +355 -0
  186. package/src/use-super-admin-context.ts +93 -0
  187. package/src/use-tenant-action-flow.ts +238 -0
  188. package/src/use-tenant-billing-catalog.ts +160 -0
  189. package/src/use-tenant-billing.ts +453 -0
  190. package/src/use-tenant-manifest.ts +94 -0
  191. package/src/use-tenant-subscription-bundles.ts +148 -0
  192. package/src/use-tenants.ts +61 -0
  193. package/src/version.ts +10 -0
@@ -0,0 +1,448 @@
1
+ <template>
2
+ <div class="mc-admin">
3
+ <div class="mc-admin-head">
4
+ <div style="flex: 1">
5
+ <div class="mc-admin-title">Marketing-Verwaltung</div>
6
+ <div class="mc-admin-sub">
7
+ Sichtbarkeit, Reihenfolge &amp; Highlight-Badges pro Plan · Edits gehen direkt
8
+ live
9
+ </div>
10
+ </div>
11
+ </div>
12
+
13
+ <div class="mc-admin-grid">
14
+ <div class="mc-admin-thead">
15
+ <div>Plan</div>
16
+ <div>Sichtbar</div>
17
+ <div>Badge</div>
18
+ <div>Priorität</div>
19
+ <div>Highlight</div>
20
+ <div></div>
21
+ </div>
22
+
23
+ <template v-for="row in adminRows" :key="row.plan.id">
24
+ <div class="mc-admin-row" :class="{ 'mc-admin-row--disabled': !row.liveVersion }">
25
+ <div>
26
+ <div class="mc-plan-cell">
27
+ <div
28
+ class="mc-plan-mark"
29
+ :style="{
30
+ background: row.accent + '15',
31
+ color: row.accent,
32
+ borderColor: row.accent + '33',
33
+ }"
34
+ >
35
+ {{ row.plan.planKey.slice(0, 3) }}
36
+ </div>
37
+ <div>
38
+ <div class="mc-plan-label">
39
+ {{ row.m.displayLabel || row.plan.label }}
40
+ </div>
41
+ <div class="mc-plan-key">{{ row.plan.planKey }}</div>
42
+ </div>
43
+ </div>
44
+ <div
45
+ v-if="row.publishedVersions.length > 1"
46
+ class="mc-version-tabs"
47
+ role="tablist"
48
+ :aria-label="`Plan-Versionen für ${row.plan.label}`"
49
+ >
50
+ <button
51
+ v-for="v in row.publishedVersions"
52
+ :key="v.id"
53
+ type="button"
54
+ role="tab"
55
+ class="mc-version-tab"
56
+ :class="{ 'mc-version-tab--active': row.liveVersion?.id === v.id }"
57
+ :title="formatVersionTitle(v)"
58
+ @click="$emit('select-version', row.plan, v.id)"
59
+ >
60
+ {{ formatVersionTab(v) }}
61
+ </button>
62
+ </div>
63
+ </div>
64
+ <div>
65
+ <label class="mc-toggle" :class="{ disabled: !row.liveVersion }">
66
+ <input
67
+ type="checkbox"
68
+ :checked="row.m.visible"
69
+ :disabled="!row.liveVersion || busy"
70
+ @change="$emit('patch', row, { visible: checked($event) })"
71
+ />
72
+ <span />
73
+ </label>
74
+ </div>
75
+ <div>
76
+ <input
77
+ class="mc-field"
78
+ style="max-width: 120px"
79
+ placeholder="—"
80
+ :value="row.m.badge"
81
+ :disabled="!row.liveVersion || busy"
82
+ @change="$emit('patch', row, { badge: textValue($event) })"
83
+ />
84
+ </div>
85
+ <div>
86
+ <input
87
+ type="number"
88
+ min="0"
89
+ max="9999"
90
+ class="mc-field"
91
+ style="max-width: 72px"
92
+ :value="row.m.priority"
93
+ :disabled="!row.liveVersion || busy"
94
+ @change="$emit('patch', row, { priority: numberValue($event) })"
95
+ />
96
+ </div>
97
+ <div>
98
+ <label class="mc-toggle" :class="{ disabled: !row.liveVersion }">
99
+ <input
100
+ type="checkbox"
101
+ :checked="row.m.highlight"
102
+ :disabled="!row.liveVersion || busy"
103
+ @change="$emit('patch', row, { highlight: checked($event) })"
104
+ />
105
+ <span />
106
+ </label>
107
+ </div>
108
+ <div class="mc-admin-row-end">
109
+ <span v-if="!row.liveVersion" class="mc-chip mc-chip--muted">
110
+ keine Live-Version
111
+ </span>
112
+ <span v-else-if="!row.m.visible" class="mc-chip mc-chip--muted">
113
+ versteckt
114
+ </span>
115
+ <span v-else-if="row.m.highlight" class="mc-chip mc-chip--featured">
116
+ Featured
117
+ </span>
118
+ <span v-else class="mc-chip mc-chip--live">live</span>
119
+ <button
120
+ v-if="row.liveVersion"
121
+ type="button"
122
+ class="mc-expand-btn"
123
+ :title="
124
+ expandedKey === row.plan.planKey
125
+ ? 'Schließen'
126
+ : 'Teaser, Trial & Top-Features bearbeiten'
127
+ "
128
+ @click="$emit('toggle-expand', row)"
129
+ >
130
+ <span
131
+ class="mc-chev"
132
+ :class="{ open: expandedKey === row.plan.planKey }"
133
+ >
134
+ <svg
135
+ width="14"
136
+ height="14"
137
+ viewBox="0 0 24 24"
138
+ fill="none"
139
+ stroke="currentColor"
140
+ stroke-width="2.5"
141
+ >
142
+ <path d="m9 18 6-6-6-6" />
143
+ </svg>
144
+ </span>
145
+ </button>
146
+ </div>
147
+ </div>
148
+
149
+ <div v-if="expandedKey === row.plan.planKey" class="mc-admin-expand">
150
+ <div class="mc-expand-grid">
151
+ <div class="mc-expand-col">
152
+ <div class="mc-expand-sec">
153
+ <div class="mc-field-head">
154
+ <label class="mc-expand-label">Plan-Name</label>
155
+ <span
156
+ v-if="activeLocale !== defaultLocale"
157
+ class="mc-source-hint"
158
+ >
159
+ {{ defaultLocale.toUpperCase() }}:
160
+ <em>„{{ row.plan.label }}"</em>
161
+ </span>
162
+ </div>
163
+ <div v-if="activeLocale === defaultLocale" class="mc-locked-value">
164
+ <span>{{ row.plan.label }}</span>
165
+ <span class="mc-locked-hint">
166
+ wird in den Plan-Stammdaten gepflegt
167
+ </span>
168
+ </div>
169
+ <input
170
+ v-else
171
+ class="mc-field"
172
+ :placeholder="row.plan.label"
173
+ :value="row.m.displayLabel"
174
+ :disabled="busy"
175
+ @change="$emit('patch-display-label', row, textValue($event))"
176
+ />
177
+ <div
178
+ v-if="activeLocale !== defaultLocale && !row.m.displayLabel"
179
+ class="mc-expand-hint"
180
+ >
181
+ leer · Fallback auf „{{ row.plan.label }}" wird verwendet
182
+ </div>
183
+ </div>
184
+
185
+ <div class="mc-expand-sec">
186
+ <label class="mc-expand-label">Teaser-Text</label>
187
+ <textarea
188
+ class="mc-field mc-field--area"
189
+ rows="2"
190
+ placeholder="Kurzbeschreibung für die Pricing-Page …"
191
+ :value="row.m.description"
192
+ :disabled="busy"
193
+ @change="
194
+ $emit('patch', row, { description: textValue($event) })
195
+ "
196
+ />
197
+ <div class="mc-expand-hint">
198
+ erscheint unter dem Plannamen in der Vorschau
199
+ </div>
200
+ </div>
201
+
202
+ <div class="mc-expand-sec">
203
+ <label class="mc-expand-label">Kostenlose Testphase</label>
204
+ <div class="mc-trial-row">
205
+ <label class="mc-toggle">
206
+ <input
207
+ type="checkbox"
208
+ :checked="row.m.trialEnabled"
209
+ :disabled="busy"
210
+ @change="
211
+ $emit('patch', row, {
212
+ trialEnabled: checked($event),
213
+ })
214
+ "
215
+ />
216
+ <span />
217
+ </label>
218
+ <span class="mc-trial-label">
219
+ {{ row.m.trialEnabled ? 'Trial aktiv' : 'Kein Trial' }}
220
+ </span>
221
+ <span class="mc-trial-days">
222
+ <input
223
+ type="number"
224
+ min="1"
225
+ max="365"
226
+ class="mc-field"
227
+ style="max-width: 72px"
228
+ :value="row.m.trialDays"
229
+ :disabled="!row.m.trialEnabled || busy"
230
+ @change="
231
+ $emit('patch', row, {
232
+ trialDays: numberValue($event),
233
+ })
234
+ "
235
+ />
236
+ <span class="mc-trial-unit">Tage</span>
237
+ </span>
238
+ </div>
239
+ <div class="mc-expand-hint">
240
+ CTA-Text generiert sich automatisch („Jetzt
241
+ {{ row.m.trialDays }} Tage testen“).
242
+ </div>
243
+ </div>
244
+
245
+ <div class="mc-expand-sec">
246
+ <label class="mc-expand-label"
247
+ >CTA-Text überschreiben (optional)</label
248
+ >
249
+ <input
250
+ class="mc-field"
251
+ :placeholder="autoCtaText(row)"
252
+ :value="row.m.ctaLabel ?? ''"
253
+ :disabled="busy"
254
+ @change="
255
+ $emit('patch', row, {
256
+ ctaLabel: ctaValue(textValue($event)),
257
+ })
258
+ "
259
+ />
260
+ <div class="mc-expand-hint">leer lassen für Auto-Text</div>
261
+ </div>
262
+ </div>
263
+
264
+ <div class="mc-expand-col">
265
+ <div class="mc-expand-sec">
266
+ <div class="mc-tf-head">
267
+ <label class="mc-expand-label" style="margin: 0">
268
+ Top-Features
269
+ </label>
270
+ <span class="mc-expand-hint" style="margin-left: auto">
271
+ {{ editFeatures.length }} Einträge
272
+ </span>
273
+ </div>
274
+
275
+ <div class="mc-tf-list">
276
+ <div v-for="(f, i) in editFeatures" :key="i" class="mc-tf-row">
277
+ <span class="mc-tf-num">{{ i + 1 }}</span>
278
+ <input
279
+ class="mc-field mc-tf-label"
280
+ :placeholder="
281
+ f.key
282
+ ? resolveComponentLabel(f.key)
283
+ : 'z. B. Beitragsverwaltung'
284
+ "
285
+ :value="f.label"
286
+ :disabled="busy"
287
+ @input="
288
+ $emit('update-feature-label', i, textValue($event))
289
+ "
290
+ @change="$emit('persist-features', row)"
291
+ />
292
+ <input
293
+ class="mc-field mc-tf-strong"
294
+ placeholder="Highlight, z. B. bis 100"
295
+ :value="f.strong"
296
+ :disabled="busy"
297
+ @input="
298
+ $emit('update-feature-strong', i, textValue($event))
299
+ "
300
+ @change="$emit('persist-features', row)"
301
+ />
302
+ <div class="mc-tf-actions">
303
+ <button
304
+ type="button"
305
+ class="mc-iconbtn"
306
+ title="Nach oben"
307
+ :disabled="i === 0 || busy"
308
+ @click="$emit('move-feature', row, i, -1)"
309
+ >
310
+
311
+ </button>
312
+ <button
313
+ type="button"
314
+ class="mc-iconbtn"
315
+ title="Nach unten"
316
+ :disabled="i === editFeatures.length - 1 || busy"
317
+ @click="$emit('move-feature', row, i, 1)"
318
+ >
319
+
320
+ </button>
321
+ <button
322
+ type="button"
323
+ class="mc-iconbtn mc-iconbtn--danger"
324
+ title="Entfernen"
325
+ :disabled="busy"
326
+ @click="$emit('remove-feature', row, i)"
327
+ >
328
+ <svg
329
+ width="12"
330
+ height="12"
331
+ viewBox="0 0 24 24"
332
+ fill="none"
333
+ stroke="currentColor"
334
+ stroke-width="2.5"
335
+ >
336
+ <path d="M18 6 6 18M6 6l12 12" />
337
+ </svg>
338
+ </button>
339
+ </div>
340
+ </div>
341
+ <div v-if="editFeatures.length === 0" class="mc-tf-empty">
342
+ Noch keine Top-Features — füge Plan-Komponenten oder freien
343
+ Text hinzu.
344
+ </div>
345
+ </div>
346
+
347
+ <div class="mc-tf-add">
348
+ <button
349
+ type="button"
350
+ class="mc-btn mc-btn--sm"
351
+ :disabled="busy"
352
+ @click="$emit('add-feature', row)"
353
+ >
354
+ <svg
355
+ width="13"
356
+ height="13"
357
+ viewBox="0 0 24 24"
358
+ fill="none"
359
+ stroke="currentColor"
360
+ stroke-width="2.5"
361
+ >
362
+ <path d="M12 5v14M5 12h14" />
363
+ </svg>
364
+ <span>Eigener Eintrag</span>
365
+ </button>
366
+ <div
367
+ v-if="suggestionsFor(row).length > 0"
368
+ class="mc-tf-suggestions"
369
+ >
370
+ <span class="mc-expand-hint">aus Plan-Komponenten:</span>
371
+ <button
372
+ v-for="(s, i) in suggestionsFor(row)"
373
+ :key="i"
374
+ type="button"
375
+ class="mc-tf-chip"
376
+ :disabled="busy"
377
+ :title="`„${s.label}“ als Top-Feature übernehmen`"
378
+ @click="$emit('add-suggestion', row, s)"
379
+ >
380
+ <svg
381
+ width="11"
382
+ height="11"
383
+ viewBox="0 0 24 24"
384
+ fill="none"
385
+ stroke="currentColor"
386
+ stroke-width="2.5"
387
+ >
388
+ <path d="M12 5v14M5 12h14" />
389
+ </svg>
390
+ <span>{{ s.label }}</span>
391
+ <em v-if="s.strong">· {{ s.strong }}</em>
392
+ </button>
393
+ </div>
394
+ </div>
395
+ </div>
396
+ </div>
397
+ </div>
398
+ </div>
399
+ </template>
400
+ </div>
401
+ </div>
402
+ </template>
403
+
404
+ <script setup lang="ts">
405
+ import type { MarketingTopFeature, PlanRow, PlanVersionRow } from '@saasicat/types';
406
+ import type { FeatureSuggestion, MarketingRow, ResolvedMarketing } from './types.js';
407
+
408
+ defineProps<{
409
+ adminRows: MarketingRow[];
410
+ busy: boolean;
411
+ expandedKey: string | null;
412
+ activeLocale: string;
413
+ defaultLocale: string;
414
+ editFeatures: MarketingTopFeature[];
415
+ formatVersionTitle: (version: PlanVersionRow) => string;
416
+ formatVersionTab: (version: PlanVersionRow) => string;
417
+ autoCtaText: (row: MarketingRow) => string;
418
+ ctaValue: (raw: string) => string | null;
419
+ resolveComponentLabel: (key: string) => string;
420
+ suggestionsFor: (row: MarketingRow) => FeatureSuggestion[];
421
+ }>();
422
+
423
+ defineEmits<{
424
+ (e: 'select-version', plan: PlanRow, versionId: string): void;
425
+ (e: 'patch', row: MarketingRow, partial: Partial<ResolvedMarketing>): void;
426
+ (e: 'patch-display-label', row: MarketingRow, value: string): void;
427
+ (e: 'toggle-expand', row: MarketingRow): void;
428
+ (e: 'update-feature-label', index: number, value: string): void;
429
+ (e: 'update-feature-strong', index: number, value: string): void;
430
+ (e: 'persist-features', row: MarketingRow): void;
431
+ (e: 'move-feature', row: MarketingRow, index: number, dir: -1 | 1): void;
432
+ (e: 'remove-feature', row: MarketingRow, index: number): void;
433
+ (e: 'add-feature', row: MarketingRow): void;
434
+ (e: 'add-suggestion', row: MarketingRow, suggestion: FeatureSuggestion): void;
435
+ }>();
436
+
437
+ function textValue(event: Event): string {
438
+ return (event.target as HTMLInputElement | HTMLTextAreaElement | null)?.value ?? '';
439
+ }
440
+
441
+ function numberValue(event: Event): number {
442
+ return Number((event.target as HTMLInputElement | null)?.value ?? 0);
443
+ }
444
+
445
+ function checked(event: Event): boolean {
446
+ return (event.target as HTMLInputElement | null)?.checked ?? false;
447
+ }
448
+ </script>
@@ -0,0 +1,93 @@
1
+ <template>
2
+ <div class="mc-page-head">
3
+ <div>
4
+ <h2 class="mc-h-title">Marketing-Catalog</h2>
5
+ <p class="mc-h-sub">
6
+ Öffentliche Marketing-Projektion · gefilterte Sicht für Website, Pricing-Page und
7
+ Checkout
8
+ </p>
9
+ </div>
10
+ <div class="mc-head-actions">
11
+ <div class="mc-locale-mgr">
12
+ <span class="mc-locale-mgr-label">Sprachen:</span>
13
+ <span
14
+ v-for="loc in activeLocaleSet"
15
+ :key="loc"
16
+ class="mc-locale-pill"
17
+ :class="{ active: loc === activeLocale }"
18
+ >
19
+ <button
20
+ type="button"
21
+ class="mc-locale-pill-btn"
22
+ @click="$emit('localeChange', loc)"
23
+ >
24
+ {{ loc.toUpperCase() }}
25
+ <span v-if="loc === defaultLocale" title="Default-Sprache">★</span>
26
+ </button>
27
+ <button
28
+ v-if="loc !== defaultLocale"
29
+ type="button"
30
+ class="mc-locale-x"
31
+ title="Sprache deaktivieren"
32
+ @click="$emit('removeLocale', loc)"
33
+ >
34
+ ×
35
+ </button>
36
+ </span>
37
+ <div class="mc-locale-add-wrap">
38
+ <button
39
+ type="button"
40
+ class="mc-locale-add"
41
+ :disabled="addableLocales.length === 0"
42
+ @click="$emit('update:localePickerOpen', !localePickerOpen)"
43
+ >
44
+ + Sprache
45
+ </button>
46
+ <div v-if="localePickerOpen" class="mc-locale-picker">
47
+ <button
48
+ v-for="l in addableLocales"
49
+ :key="l"
50
+ type="button"
51
+ class="mc-locale-picker-row"
52
+ @click="$emit('addLocale', l)"
53
+ >
54
+ {{ l.toUpperCase() }}
55
+ </button>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ <button class="mc-btn" type="button" :disabled="busy" @click="$emit('reload')">
60
+ <svg
61
+ width="14"
62
+ height="14"
63
+ viewBox="0 0 24 24"
64
+ fill="none"
65
+ stroke="currentColor"
66
+ stroke-width="2"
67
+ >
68
+ <path d="M21 12a9 9 0 1 1-3-6.7M21 3v6h-6" />
69
+ </svg>
70
+ <span>Aktualisieren</span>
71
+ </button>
72
+ </div>
73
+ </div>
74
+ </template>
75
+
76
+ <script setup lang="ts">
77
+ defineProps<{
78
+ activeLocaleSet: string[];
79
+ activeLocale: string;
80
+ defaultLocale: string;
81
+ addableLocales: string[];
82
+ localePickerOpen: boolean;
83
+ busy: boolean;
84
+ }>();
85
+
86
+ defineEmits<{
87
+ (e: 'localeChange', loc: string): void;
88
+ (e: 'removeLocale', loc: string): void;
89
+ (e: 'addLocale', loc: string): void;
90
+ (e: 'update:localePickerOpen', value: boolean): void;
91
+ (e: 'reload'): void;
92
+ }>();
93
+ </script>