@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.
- package/README.md +64 -4
- package/dist/chunk-A3W2N26K.js +714 -0
- package/dist/chunk-QZOSDCNP.js +98 -0
- package/dist/chunk-X5SQ5HMB.js +3231 -0
- package/dist/client/index.cjs +3988 -0
- package/dist/client/index.d.cts +321 -0
- package/dist/client/index.d.ts +321 -0
- package/dist/client/index.js +70 -0
- package/dist/index.cjs +4446 -1249
- package/dist/index.d.cts +297 -888
- package/dist/index.d.ts +297 -888
- package/dist/index.js +609 -1348
- package/dist/messages-7b0P8W75.d.cts +1652 -0
- package/dist/messages-7b0P8W75.d.ts +1652 -0
- package/dist/quasar/index.cjs +3340 -0
- package/dist/quasar/index.d.cts +95 -0
- package/dist/quasar/index.d.ts +95 -0
- package/dist/quasar/index.js +96 -0
- package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
- package/dist/testing-e2e/admin-pages-suite.js +14 -11
- package/dist/use-super-admin-i18n-B3v3-SWZ.d.ts +210 -0
- package/dist/use-super-admin-i18n-DMktRVEt.d.cts +210 -0
- package/package.json +23 -3
- package/src/{action-registry.ts → client/action-registry.ts} +4 -4
- package/src/{batch-column-fetcher.ts → client/batch-column-fetcher.ts} +4 -4
- package/src/{boot-loader.ts → client/boot-loader.ts} +3 -3
- package/src/client/i18n/currency.ts +42 -0
- package/src/client/i18n/define.ts +59 -0
- package/src/client/i18n/format.ts +12 -0
- package/src/client/i18n/index.ts +14 -0
- package/src/client/i18n/locale.ts +15 -0
- package/src/client/i18n/messages/audit.ts +42 -0
- package/src/client/i18n/messages/bundles.ts +459 -0
- package/src/client/i18n/messages/common.ts +147 -0
- package/src/client/i18n/messages/dashboard.ts +16 -0
- package/src/client/i18n/messages/discovery.ts +229 -0
- package/src/client/i18n/messages/email.ts +125 -0
- package/src/client/i18n/messages/marketing.ts +270 -0
- package/src/client/i18n/messages/nav.ts +72 -0
- package/src/client/i18n/messages/pilots.ts +192 -0
- package/src/client/i18n/messages/plan-detail.ts +266 -0
- package/src/client/i18n/messages/plan-editor.ts +177 -0
- package/src/client/i18n/messages/plan-versions.ts +251 -0
- package/src/client/i18n/messages/plans.ts +503 -0
- package/src/client/i18n/messages/promos.ts +202 -0
- package/src/client/i18n/messages/shell.ts +145 -0
- package/src/client/i18n/messages/tenants.ts +104 -0
- package/src/client/i18n/messages/users.ts +78 -0
- package/src/client/i18n/messages.ts +84 -0
- package/src/client/index.ts +27 -0
- package/src/{manifest-loader.ts → client/manifest-loader.ts} +4 -4
- package/src/{nav-builder.ts → client/nav-builder.ts} +44 -51
- package/src/{plan-versions-catalog.ts → client/plan-versions-catalog.ts} +42 -30
- package/src/components/BundleVersionPublishDialog.vue +35 -24
- package/src/components/FeatureGate.vue +1 -1
- package/src/components/MarketingPromotionsTab.vue +112 -59
- package/src/components/MfaPromptDialog.vue +11 -11
- package/src/components/TenantActionConfirmDialog.vue +19 -17
- package/src/components/VersionDiffPreview.vue +72 -43
- package/src/components/bundle-editor/BundleCreatePanel.vue +76 -63
- package/src/components/bundle-editor/BundleFeaturesEditor.vue +13 -6
- package/src/components/bundle-editor/BundlePlanCompatPicker.vue +19 -14
- package/src/components/bundle-editor/BundleQuotasEditor.vue +7 -4
- package/src/components/bundle-editor/BundleStatusBanner.vue +36 -19
- package/src/components/bundle-editor/BundleVersionInlineEditor.vue +51 -35
- package/src/components/bundle-editor/BundleVersionStrip.vue +24 -12
- package/src/components/bundle-editor/bundle-version-status.ts +29 -29
- package/src/components/dialogs/PilotCreateDialog.vue +58 -63
- package/src/components/dialogs/PilotEditDialog.vue +40 -34
- package/src/components/dialogs/PromoCodeCreateDialog.vue +89 -63
- package/src/components/dialogs/PromoCodeEditDialog.vue +98 -66
- package/src/components/dialogs/types.ts +3 -13
- package/src/components/plan/PlanCycleToggle.vue +21 -10
- package/src/components/plan/PlanGrid.vue +2 -2
- package/src/components/plan/PriceSummary.vue +1 -1
- package/src/components/plan/PromoCodeInput.vue +1 -1
- package/src/components/plan/PublicBundleGrid.vue +1 -1
- package/src/components/plan-cockpit/PlanCockpit.vue +34 -15
- package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +13 -6
- package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +18 -9
- package/src/components/plan-cockpit/PlanCockpitHeader.vue +9 -6
- package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +24 -11
- package/src/components/plan-cockpit/PlanCockpitKpis.vue +28 -17
- package/src/components/plan-cockpit/PlanCockpitVersions.vue +50 -25
- package/src/components/plan-create-dialog/PlanCreateDialog.vue +45 -35
- package/src/components/plan-detail/PlanAuditLog.vue +11 -6
- package/src/components/plan-detail/PlanDetail.vue +21 -23
- package/src/components/plan-detail/PlanDetailHeader.vue +20 -11
- package/src/components/plan-detail/PlanDetailKpis.vue +30 -17
- package/src/components/plan-detail/PlanTerminateDialog.vue +22 -9
- package/src/components/plan-detail/PlanVersionDiffPanel.vue +61 -27
- package/src/components/plan-detail/PlanVersionsPanel.vue +71 -36
- package/src/components/plan-list/PlanList.vue +99 -57
- package/src/components/plan-matrix/PlanMatrix.vue +85 -38
- package/src/components/plan-review/PlanReview.vue +105 -80
- package/src/components/plan-version-editor/PlanCatalogPreview.vue +43 -20
- package/src/components/plan-version-editor/PlanComponentPool.vue +15 -7
- package/src/components/plan-version-editor/PlanVersionBasket.vue +45 -24
- package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +8 -7
- package/src/components/plan-version-editor/PlanVersionEditor.vue +74 -40
- package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +24 -12
- package/src/index.ts +82 -79
- package/src/pages-standard/AdminLayout.vue +43 -18
- package/src/pages-standard/AdminManifestErrorPage.vue +10 -10
- package/src/pages-standard/AuditPage.vue +58 -19
- package/src/pages-standard/BundlesPage.vue +29 -18
- package/src/pages-standard/DashboardPage.vue +31 -17
- package/src/pages-standard/DiscoveryPage.vue +40 -28
- package/src/pages-standard/EmailHistoryPage.vue +98 -69
- package/src/pages-standard/MarketingCatalogPage.vue +40 -21
- package/src/pages-standard/PilotsPage.vue +64 -52
- package/src/pages-standard/PlanVersionsPage.vue +9 -5
- package/src/pages-standard/PlansPage.vue +91 -59
- package/src/pages-standard/PlatformEmailPage.vue +120 -53
- package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
- package/src/pages-standard/PromoCodesPage.vue +51 -54
- package/src/pages-standard/SubscriptionsPage.vue +14 -9
- package/src/pages-standard/SuperAdminLoginPage.vue +19 -16
- package/src/pages-standard/SuperAdminSetupWizard.vue +53 -49
- package/src/pages-standard/TenantDetailPage.vue +54 -40
- package/src/pages-standard/TenantsPage.vue +57 -44
- package/src/pages-standard/UsersPage.vue +70 -52
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -8
- package/src/pages-standard/bundles-page/BundleDetailPanel.vue +28 -18
- package/src/pages-standard/bundles-page/BundlesFilterBar.vue +4 -1
- package/src/pages-standard/bundles-page/BundlesHeader.vue +9 -8
- package/src/pages-standard/bundles-page/BundlesKpis.vue +29 -11
- package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +20 -8
- package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -3
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +43 -24
- package/src/pages-standard/discovery-page/DiscoveryHeader.vue +7 -4
- package/src/pages-standard/discovery-page/DiscoveryKpis.vue +28 -15
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +20 -10
- package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +9 -6
- package/src/pages-standard/discovery-page/discovery-ui.ts +40 -52
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +81 -49
- package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +13 -10
- package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +24 -20
- package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +6 -3
- package/src/pages-standard/plan-versions/PlanDiffCard.vue +16 -6
- package/src/pages-standard/plan-versions/PlanVersionHeader.vue +42 -26
- package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +46 -27
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +8 -6
- package/src/pages-standard/plan-versions/PlanVersionsList.vue +40 -17
- package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +1 -1
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +25 -13
- package/src/pages-standard/plan-versions/format.ts +33 -17
- package/src/pages-standard/plans-page/PlanArchiveDialog.vue +12 -8
- package/src/pages-standard/plans-page/PlanBundleOverview.vue +15 -6
- package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +18 -7
- package/src/pages-standard/plans-page/PlanPublishDialog.vue +23 -12
- package/src/pages-standard/tenants/format.ts +3 -2
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +73 -38
- package/src/pages-tenant/OnboardingConfigurator.vue +3 -3
- package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
- package/src/pages-tenant/PlanChangeWizard.vue +10 -6
- package/src/pages-tenant/TenantPlanSection.vue +20 -16
- package/src/pages-tenant/default-i18n.ts +217 -1
- package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +9 -7
- package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +11 -12
- package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
- package/src/pages-tenant/tenant-plan-section/TenantPlanCardHeader.vue +1 -1
- package/src/pages-tenant/tenant-plan-section/TenantUsageGrid.vue +1 -1
- package/src/quasar/create-super-admin-app.ts +192 -0
- package/src/quasar/index.ts +11 -0
- package/src/quasar/notify.ts +30 -0
- package/src/testing-e2e/admin-pages-suite.ts +14 -11
- package/src/{manifest-store-factory.ts → vue/manifest-store-factory.ts} +1 -1
- package/src/{platform-loaders.ts → vue/platform-loaders.ts} +4 -4
- package/src/{project-page-host.ts → vue/project-page-host.ts} +6 -4
- package/src/{create-super-admin-app.ts → vue/super-admin-context.ts} +20 -139
- package/src/vue/ui-notify.ts +30 -0
- package/src/{use-actions.ts → vue/use-actions.ts} +1 -1
- package/src/{use-api-list.ts → vue/use-api-list.ts} +1 -1
- package/src/{use-audit-entries.ts → vue/use-audit-entries.ts} +3 -3
- package/src/{use-batch-columns.ts → vue/use-batch-columns.ts} +1 -1
- package/src/{use-bulk-publish.ts → vue/use-bulk-publish.ts} +7 -5
- package/src/{use-bundle-versions-map.ts → vue/use-bundle-versions-map.ts} +7 -5
- package/src/{use-bundles.ts → vue/use-bundles.ts} +14 -14
- package/src/{use-catalog-entries.ts → vue/use-catalog-entries.ts} +14 -11
- package/src/{use-discovery.ts → vue/use-discovery.ts} +9 -6
- package/src/{use-entitlement.ts → vue/use-entitlement.ts} +4 -4
- package/src/{use-live-plan-versions.ts → vue/use-live-plan-versions.ts} +5 -5
- package/src/{use-manifest.ts → vue/use-manifest.ts} +1 -1
- package/src/{use-marketing-projections.ts → vue/use-marketing-projections.ts} +10 -7
- package/src/{use-nav.ts → vue/use-nav.ts} +9 -3
- package/src/{use-plan-editor.ts → vue/use-plan-editor.ts} +2 -2
- package/src/{use-plan-versions.ts → vue/use-plan-versions.ts} +3 -3
- package/src/{use-plans.ts → vue/use-plans.ts} +13 -13
- package/src/{use-platform-tenant-actions.ts → vue/use-platform-tenant-actions.ts} +9 -6
- package/src/{use-promotions.ts → vue/use-promotions.ts} +10 -11
- package/src/{use-public-boot.ts → vue/use-public-boot.ts} +1 -1
- package/src/{use-subscription-draft.ts → vue/use-subscription-draft.ts} +2 -3
- package/src/{use-super-admin-context.ts → vue/use-super-admin-context.ts} +5 -5
- package/src/vue/use-super-admin-i18n.ts +82 -0
- package/src/{use-tenant-action-flow.ts → vue/use-tenant-action-flow.ts} +12 -7
- package/src/{use-tenant-billing-catalog.ts → vue/use-tenant-billing-catalog.ts} +2 -2
- package/src/{use-tenant-billing.ts → vue/use-tenant-billing.ts} +2 -2
- package/src/{use-tenant-manifest.ts → vue/use-tenant-manifest.ts} +2 -4
- package/src/{use-tenant-subscription-bundles.ts → vue/use-tenant-subscription-bundles.ts} +5 -5
- package/src/{use-tenants.ts → vue/use-tenants.ts} +3 -3
- package/dist/testing-e2e/admin-pages-suite.d.cts +0 -37
- package/dist/testing-e2e/admin-pages-suite.d.ts +0 -37
- package/src/components/BusinessTypeVersionEditorDialog.vue +0 -417
- package/src/components/BusinessTypeVersionPublishDialog.vue +0 -258
- package/src/pages-standard/BusinessTypesPage.vue +0 -662
- package/src/pages-standard/plan-versions/VersionDiffPreview.vue +0 -167
- package/src/use-business-types.ts +0 -269
- /package/src/{http-json.ts → client/http-json.ts} +0 -0
- /package/src/{types.ts → client/types.ts} +0 -0
- /package/src/{version.ts → client/version.ts} +0 -0
- /package/src/{create-admin-routes.ts → vue/create-admin-routes.ts} +0 -0
- /package/src/{entitlement-provider.ts → vue/entitlement-provider.ts} +0 -0
- /package/src/{feature-router-guard.ts → vue/feature-router-guard.ts} +0 -0
|
@@ -3,25 +3,28 @@
|
|
|
3
3
|
<!-- Head -->
|
|
4
4
|
<div class="mc-promo-head">
|
|
5
5
|
<div class="mc-promo-head-text">
|
|
6
|
-
<div class="mc-promo-title">
|
|
7
|
-
<div class="mc-promo-subtitle">
|
|
8
|
-
Zeitlich begrenzte Preis-Aktionen — überschreiben den regulären Preis auf der
|
|
9
|
-
Pricing-Page (SPEC_V2 §9a).
|
|
10
|
-
</div>
|
|
6
|
+
<div class="mc-promo-title">{{ msg.promotionsTab.title }}</div>
|
|
7
|
+
<div class="mc-promo-subtitle">{{ msg.promotionsTab.subtitle }}</div>
|
|
11
8
|
</div>
|
|
12
9
|
<div class="mc-promo-stats">
|
|
13
|
-
<span class="mc-promo-stat active">
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
<span class="mc-promo-stat active">
|
|
11
|
+
{{ counts.active }} {{ msg.promotionsTab.statusActive }}
|
|
12
|
+
</span>
|
|
13
|
+
<span class="mc-promo-stat scheduled">
|
|
14
|
+
{{ counts.scheduled }} {{ msg.promotionsTab.statusScheduled }}
|
|
15
|
+
</span>
|
|
16
|
+
<span class="mc-promo-stat expired">
|
|
17
|
+
{{ counts.expired }} {{ msg.promotionsTab.statusExpired }}
|
|
18
|
+
</span>
|
|
16
19
|
</div>
|
|
17
20
|
<button class="mc-promo-add" type="button" :disabled="busy" @click="onAdd">
|
|
18
|
-
|
|
21
|
+
{{ msg.promotionsTab.add }}
|
|
19
22
|
</button>
|
|
20
23
|
</div>
|
|
21
24
|
|
|
22
25
|
<!-- Timeline -->
|
|
23
26
|
<div v-if="promotions.length > 0" class="mc-promo-timeline">
|
|
24
|
-
<div class="mc-promo-timeline-head">
|
|
27
|
+
<div class="mc-promo-timeline-head">{{ msg.promotionsTab.timelineHead }}</div>
|
|
25
28
|
<div class="mc-promo-timeline-chart" :style="{ height: `${timelineHeight}px` }">
|
|
26
29
|
<div
|
|
27
30
|
v-for="t in ticks"
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
<span class="mc-promo-tick-label">{{ t.label }}</span>
|
|
33
36
|
</div>
|
|
34
37
|
<div class="mc-promo-today" :style="{ left: `${todayX}%` }">
|
|
35
|
-
<span>
|
|
38
|
+
<span>{{ msg.promotionsTab.today }}</span>
|
|
36
39
|
</div>
|
|
37
40
|
<div
|
|
38
41
|
v-for="bar in bars"
|
|
@@ -54,8 +57,8 @@
|
|
|
54
57
|
</div>
|
|
55
58
|
|
|
56
59
|
<div v-if="promotions.length === 0" class="mc-promo-empty">
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
{{ msg.promotionsTab.emptyBefore }} <strong>{{ msg.promotionsTab.add }}</strong>
|
|
61
|
+
{{ msg.promotionsTab.emptyAfter }}
|
|
59
62
|
</div>
|
|
60
63
|
|
|
61
64
|
<!-- List -->
|
|
@@ -72,7 +75,7 @@
|
|
|
72
75
|
<div class="mc-promo-row-sub">
|
|
73
76
|
<span class="mc-promo-typechip">{{ typeChip(p) }}</span>
|
|
74
77
|
<span v-if="p.appliesTo.length === 0" class="mc-promo-muted">
|
|
75
|
-
|
|
78
|
+
{{ msg.promotionsTab.noPlans }}
|
|
76
79
|
</span>
|
|
77
80
|
<span v-for="k in p.appliesTo" :key="k" class="mc-promo-planchip">
|
|
78
81
|
{{ k }}
|
|
@@ -84,7 +87,8 @@
|
|
|
84
87
|
<span class="mc-promo-cycle">
|
|
85
88
|
{{ cycleLabel(p.billingCycle) }}
|
|
86
89
|
<template v-if="p.onlyLocales">
|
|
87
|
-
·
|
|
90
|
+
· {{ msg.promotionsTab.onlyPrefix }}
|
|
91
|
+
{{ p.onlyLocales.join(', ').toUpperCase() }}
|
|
88
92
|
</template>
|
|
89
93
|
</span>
|
|
90
94
|
</div>
|
|
@@ -97,17 +101,19 @@
|
|
|
97
101
|
<div v-if="expandedId === p.id" class="mc-promo-editor">
|
|
98
102
|
<div class="mc-promo-editor-grid">
|
|
99
103
|
<div class="mc-promo-editor-col">
|
|
100
|
-
<label class="mc-promo-label">
|
|
104
|
+
<label class="mc-promo-label">
|
|
105
|
+
{{ msg.promotionsTab.internalLabelLabel }}
|
|
106
|
+
</label>
|
|
101
107
|
<input
|
|
102
108
|
class="mc-promo-input"
|
|
103
109
|
:value="p.internalLabel"
|
|
104
110
|
@change="patch(p, { internalLabel: inputVal($event) })"
|
|
105
111
|
/>
|
|
106
112
|
|
|
107
|
-
<label class="mc-promo-label">
|
|
113
|
+
<label class="mc-promo-label">{{ common.type }}</label>
|
|
108
114
|
<div class="mc-promo-typegrid">
|
|
109
115
|
<button
|
|
110
|
-
v-for="t in
|
|
116
|
+
v-for="t in typeOptions"
|
|
111
117
|
:key="t.id"
|
|
112
118
|
type="button"
|
|
113
119
|
class="mc-promo-typeopt"
|
|
@@ -118,7 +124,7 @@
|
|
|
118
124
|
</button>
|
|
119
125
|
</div>
|
|
120
126
|
|
|
121
|
-
<label class="mc-promo-label">
|
|
127
|
+
<label class="mc-promo-label">{{ msg.promotionsTab.valueLabel }}</label>
|
|
122
128
|
<div class="mc-promo-valrow">
|
|
123
129
|
<template v-if="p.type === 'percent' || p.type === 'amount'">
|
|
124
130
|
<input
|
|
@@ -128,39 +134,55 @@
|
|
|
128
134
|
@change="patch(p, { value: numInput($event) })"
|
|
129
135
|
/>
|
|
130
136
|
<span class="mc-promo-muted">
|
|
131
|
-
{{
|
|
137
|
+
{{
|
|
138
|
+
p.type === 'percent'
|
|
139
|
+
? msg.promotionsTab.percentUnit
|
|
140
|
+
: msg.promotionsTab.amountUnit
|
|
141
|
+
}}
|
|
132
142
|
</span>
|
|
133
143
|
</template>
|
|
134
144
|
<template v-else-if="p.type === 'intro'">
|
|
135
|
-
<span class="mc-promo-muted">
|
|
145
|
+
<span class="mc-promo-muted">
|
|
146
|
+
{{ msg.promotionsTab.introForPrefix }}
|
|
147
|
+
</span>
|
|
136
148
|
<input
|
|
137
149
|
class="mc-promo-input mc-promo-input--sm"
|
|
138
150
|
type="number"
|
|
139
151
|
:value="introMonths(p)"
|
|
140
152
|
@change="patchIntro(p, 'months', numInput($event))"
|
|
141
153
|
/>
|
|
142
|
-
<span class="mc-promo-muted">
|
|
154
|
+
<span class="mc-promo-muted">
|
|
155
|
+
{{ msg.promotionsTab.introMonthsUnit }}
|
|
156
|
+
</span>
|
|
143
157
|
<input
|
|
144
158
|
class="mc-promo-input mc-promo-input--sm"
|
|
145
159
|
type="number"
|
|
146
160
|
:value="introPrice(p)"
|
|
147
161
|
@change="patchIntro(p, 'price', numInput($event))"
|
|
148
162
|
/>
|
|
149
|
-
<span class="mc-promo-muted"
|
|
163
|
+
<span class="mc-promo-muted">
|
|
164
|
+
{{ msg.promotionsTab.introPriceUnit }}
|
|
165
|
+
</span>
|
|
150
166
|
</template>
|
|
151
167
|
<template v-else>
|
|
152
|
-
<span class="mc-promo-muted">
|
|
168
|
+
<span class="mc-promo-muted">
|
|
169
|
+
{{ msg.promotionsTab.freeMonthsPrefix }}
|
|
170
|
+
</span>
|
|
153
171
|
<input
|
|
154
172
|
class="mc-promo-input mc-promo-input--sm"
|
|
155
173
|
type="number"
|
|
156
174
|
:value="numValue(p)"
|
|
157
175
|
@change="patch(p, { value: numInput($event) })"
|
|
158
176
|
/>
|
|
159
|
-
<span class="mc-promo-muted">
|
|
177
|
+
<span class="mc-promo-muted">
|
|
178
|
+
{{ msg.promotionsTab.freeMonthsUnit }}
|
|
179
|
+
</span>
|
|
160
180
|
</template>
|
|
161
181
|
</div>
|
|
162
182
|
|
|
163
|
-
<label class="mc-promo-label">
|
|
183
|
+
<label class="mc-promo-label">
|
|
184
|
+
{{ common.validity }}
|
|
185
|
+
</label>
|
|
164
186
|
<div class="mc-promo-valrow">
|
|
165
187
|
<input
|
|
166
188
|
class="mc-promo-input"
|
|
@@ -177,10 +199,12 @@
|
|
|
177
199
|
/>
|
|
178
200
|
</div>
|
|
179
201
|
|
|
180
|
-
<label class="mc-promo-label">
|
|
202
|
+
<label class="mc-promo-label">
|
|
203
|
+
{{ msg.promotionsTab.billingCycleLabel }}
|
|
204
|
+
</label>
|
|
181
205
|
<div class="mc-promo-typegrid">
|
|
182
206
|
<button
|
|
183
|
-
v-for="c in
|
|
207
|
+
v-for="c in cycleOptions"
|
|
184
208
|
:key="c.id"
|
|
185
209
|
type="button"
|
|
186
210
|
class="mc-promo-typeopt"
|
|
@@ -191,7 +215,9 @@
|
|
|
191
215
|
</button>
|
|
192
216
|
</div>
|
|
193
217
|
|
|
194
|
-
<label class="mc-promo-label">
|
|
218
|
+
<label class="mc-promo-label">
|
|
219
|
+
{{ msg.promotionsTab.priorityLabel }}
|
|
220
|
+
</label>
|
|
195
221
|
<input
|
|
196
222
|
class="mc-promo-input mc-promo-input--sm"
|
|
197
223
|
type="number"
|
|
@@ -201,7 +227,9 @@
|
|
|
201
227
|
</div>
|
|
202
228
|
|
|
203
229
|
<div class="mc-promo-editor-col">
|
|
204
|
-
<label class="mc-promo-label">
|
|
230
|
+
<label class="mc-promo-label">
|
|
231
|
+
{{ msg.promotionsTab.appliesToLabel }}
|
|
232
|
+
</label>
|
|
205
233
|
<div class="mc-promo-planlist">
|
|
206
234
|
<button
|
|
207
235
|
v-for="pl in plans"
|
|
@@ -216,7 +244,9 @@
|
|
|
216
244
|
</button>
|
|
217
245
|
</div>
|
|
218
246
|
|
|
219
|
-
<label class="mc-promo-label">
|
|
247
|
+
<label class="mc-promo-label">
|
|
248
|
+
{{ msg.promotionsTab.localeRestrictionLabel }}
|
|
249
|
+
</label>
|
|
220
250
|
<div class="mc-promo-typegrid">
|
|
221
251
|
<button
|
|
222
252
|
type="button"
|
|
@@ -224,7 +254,7 @@
|
|
|
224
254
|
:class="{ active: !p.onlyLocales }"
|
|
225
255
|
@click="patch(p, { onlyLocales: null })"
|
|
226
256
|
>
|
|
227
|
-
|
|
257
|
+
{{ msg.promotionsTab.allLocales }}
|
|
228
258
|
</button>
|
|
229
259
|
<button
|
|
230
260
|
v-for="l in activeLocales"
|
|
@@ -234,28 +264,30 @@
|
|
|
234
264
|
:class="{ active: p.onlyLocales?.includes(l) }"
|
|
235
265
|
@click="toggleLocale(p, l)"
|
|
236
266
|
>
|
|
237
|
-
|
|
267
|
+
{{ msg.promotionsTab.onlyPrefix }} {{ l.toUpperCase() }}
|
|
238
268
|
</button>
|
|
239
269
|
</div>
|
|
240
270
|
|
|
241
|
-
<label class="mc-promo-label"
|
|
271
|
+
<label class="mc-promo-label">
|
|
272
|
+
{{ msg.promotionsTab.translationsLabel }}
|
|
273
|
+
</label>
|
|
242
274
|
<div v-for="l in activeLocales" :key="l" class="mc-promo-i18n-block">
|
|
243
275
|
<span class="mc-promo-i18n-code">{{ l.toUpperCase() }}</span>
|
|
244
276
|
<input
|
|
245
277
|
class="mc-promo-input"
|
|
246
|
-
placeholder="
|
|
278
|
+
:placeholder="msg.promotionsTab.badgePlaceholder"
|
|
247
279
|
:value="p.i18n?.[l]?.badge || ''"
|
|
248
280
|
@change="patchI18n(p, l, 'badge', inputVal($event))"
|
|
249
281
|
/>
|
|
250
282
|
<input
|
|
251
283
|
class="mc-promo-input"
|
|
252
|
-
placeholder="
|
|
284
|
+
:placeholder="msg.promotionsTab.fineprintPlaceholder"
|
|
253
285
|
:value="p.i18n?.[l]?.fineprint || ''"
|
|
254
286
|
@change="patchI18n(p, l, 'fineprint', inputVal($event))"
|
|
255
287
|
/>
|
|
256
288
|
</div>
|
|
257
289
|
|
|
258
|
-
<label class="mc-promo-label">
|
|
290
|
+
<label class="mc-promo-label">{{ msg.promotionsTab.colorLabel }}</label>
|
|
259
291
|
<div class="mc-promo-colors">
|
|
260
292
|
<button
|
|
261
293
|
v-for="c in COLORS"
|
|
@@ -269,7 +301,7 @@
|
|
|
269
301
|
</div>
|
|
270
302
|
|
|
271
303
|
<button class="mc-promo-delete" type="button" @click="onRemove(p)">
|
|
272
|
-
|
|
304
|
+
{{ msg.promotionsTab.delete }}
|
|
273
305
|
</button>
|
|
274
306
|
</div>
|
|
275
307
|
</div>
|
|
@@ -290,6 +322,8 @@ import {
|
|
|
290
322
|
type PromotionType,
|
|
291
323
|
type UpdatePromotionData,
|
|
292
324
|
} from '@saasicat/types';
|
|
325
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
326
|
+
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
293
327
|
|
|
294
328
|
// Promotions tab of the Marketing Catalog (SPEC_V2 §9a). Standalone child
|
|
295
329
|
// component — the MarketingCatalogPage wires `usePromotions` and passes the
|
|
@@ -309,20 +343,24 @@ const props = defineProps<{
|
|
|
309
343
|
projectKey: string;
|
|
310
344
|
}>();
|
|
311
345
|
|
|
346
|
+
const msg = useSaMessages('marketing');
|
|
347
|
+
const common = useSaMessages('common');
|
|
348
|
+
const { intlLocale } = useSuperAdminI18n();
|
|
349
|
+
|
|
312
350
|
const busy = computed(() => props.busy ?? false);
|
|
313
351
|
const expandedId = ref<string | null>(null);
|
|
314
352
|
|
|
315
|
-
const
|
|
316
|
-
{ id: 'percent', label:
|
|
317
|
-
{ id: 'amount', label:
|
|
318
|
-
{ id: 'intro', label:
|
|
319
|
-
{ id: 'freeMonths', label:
|
|
320
|
-
];
|
|
321
|
-
const
|
|
322
|
-
{ id: 'monthly', label:
|
|
323
|
-
{ id: 'yearly', label:
|
|
324
|
-
{ id: 'both', label:
|
|
325
|
-
];
|
|
353
|
+
const typeOptions = computed<Array<{ id: PromotionType; label: string }>>(() => [
|
|
354
|
+
{ id: 'percent', label: msg.value.promotionsTab.typePercent },
|
|
355
|
+
{ id: 'amount', label: msg.value.promotionsTab.typeAmount },
|
|
356
|
+
{ id: 'intro', label: msg.value.promotionsTab.typeIntro },
|
|
357
|
+
{ id: 'freeMonths', label: msg.value.promotionsTab.typeFreeMonths },
|
|
358
|
+
]);
|
|
359
|
+
const cycleOptions = computed<Array<{ id: PromotionBillingCycle; label: string }>>(() => [
|
|
360
|
+
{ id: 'monthly', label: common.value.monthly },
|
|
361
|
+
{ id: 'yearly', label: common.value.yearly },
|
|
362
|
+
{ id: 'both', label: common.value.both },
|
|
363
|
+
]);
|
|
326
364
|
const COLORS = ['#10b981', '#dc2626', '#f59e0b', '#2563eb', '#7c3aed', '#0f172a'];
|
|
327
365
|
|
|
328
366
|
const today = new Date();
|
|
@@ -331,7 +369,9 @@ function statusOf(p: PromotionRow): PromotionStatus {
|
|
|
331
369
|
return promoStatus(p, today);
|
|
332
370
|
}
|
|
333
371
|
function statusLabel(s: PromotionStatus): string {
|
|
334
|
-
|
|
372
|
+
if (s === 'active') return msg.value.promotionsTab.statusActive;
|
|
373
|
+
if (s === 'scheduled') return msg.value.promotionsTab.statusScheduled;
|
|
374
|
+
return msg.value.promotionsTab.statusExpired;
|
|
335
375
|
}
|
|
336
376
|
|
|
337
377
|
const counts = computed(() => ({
|
|
@@ -353,13 +393,20 @@ function typeChip(p: PromotionRow): string {
|
|
|
353
393
|
if (p.type === 'percent' && typeof p.value === 'number') return `−${p.value}%`;
|
|
354
394
|
if (p.type === 'amount' && typeof p.value === 'number') return `−${p.value} €`;
|
|
355
395
|
if (p.type === 'intro' && typeof p.value === 'object') {
|
|
356
|
-
return
|
|
396
|
+
return formatMessage(msg.value.promotionsTab.chipIntro, {
|
|
397
|
+
price: p.value.price,
|
|
398
|
+
months: p.value.months,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
if (p.type === 'freeMonths' && typeof p.value === 'number') {
|
|
402
|
+
return formatMessage(msg.value.promotionsTab.chipFreeMonths, { months: p.value });
|
|
357
403
|
}
|
|
358
|
-
if (p.type === 'freeMonths' && typeof p.value === 'number') return `${p.value} Mo. gratis`;
|
|
359
404
|
return p.type;
|
|
360
405
|
}
|
|
361
406
|
function cycleLabel(c: PromotionBillingCycle): string {
|
|
362
|
-
|
|
407
|
+
if (c === 'both') return msg.value.promotionsTab.cycleBothShort;
|
|
408
|
+
if (c === 'monthly') return msg.value.promotionsTab.cycleMonthlyShort;
|
|
409
|
+
return msg.value.promotionsTab.cycleYearlyShort;
|
|
363
410
|
}
|
|
364
411
|
|
|
365
412
|
// ─── Timeline ───
|
|
@@ -386,6 +433,12 @@ function pct(d: Date | string): number {
|
|
|
386
433
|
return Math.max(0, Math.min(100, v));
|
|
387
434
|
}
|
|
388
435
|
const todayX = computed(() => pct(today));
|
|
436
|
+
function tickLabel(d: Date): string {
|
|
437
|
+
const month = new Date(d.getFullYear(), d.getMonth(), 1).toLocaleDateString(intlLocale.value, {
|
|
438
|
+
month: 'short',
|
|
439
|
+
});
|
|
440
|
+
return `${month} ${d.getFullYear()}`;
|
|
441
|
+
}
|
|
389
442
|
const ticks = computed(() => {
|
|
390
443
|
const out: Array<{ x: number; label: string }> = [];
|
|
391
444
|
const start = axisStart.value;
|
|
@@ -393,10 +446,7 @@ const ticks = computed(() => {
|
|
|
393
446
|
const d = new Date(Date.UTC(start.getFullYear(), start.getMonth(), 1));
|
|
394
447
|
while (d <= end) {
|
|
395
448
|
if (d.getMonth() % 3 === 0) {
|
|
396
|
-
out.push({
|
|
397
|
-
x: pct(d),
|
|
398
|
-
label: `${['Jan', 'Apr', 'Jul', 'Okt'][d.getMonth() / 3]} ${d.getFullYear()}`,
|
|
399
|
-
});
|
|
449
|
+
out.push({ x: pct(d), label: tickLabel(d) });
|
|
400
450
|
}
|
|
401
451
|
d.setMonth(d.getMonth() + 1);
|
|
402
452
|
}
|
|
@@ -492,7 +542,7 @@ function patchI18n(
|
|
|
492
542
|
async function onAdd(): Promise<void> {
|
|
493
543
|
const created = await props.create({
|
|
494
544
|
projectKey: props.projectKey,
|
|
495
|
-
internalLabel:
|
|
545
|
+
internalLabel: msg.value.promotionsTab.newPromotionLabel,
|
|
496
546
|
type: 'percent',
|
|
497
547
|
value: 10,
|
|
498
548
|
appliesTo: [],
|
|
@@ -507,7 +557,10 @@ async function onAdd(): Promise<void> {
|
|
|
507
557
|
}
|
|
508
558
|
|
|
509
559
|
async function onRemove(p: PromotionRow): Promise<void> {
|
|
510
|
-
|
|
560
|
+
const question = formatMessage(msg.value.promotionsTab.deleteConfirm, {
|
|
561
|
+
label: p.internalLabel,
|
|
562
|
+
});
|
|
563
|
+
if (!window.confirm(question)) return;
|
|
511
564
|
if (expandedId.value === p.id) expandedId.value = null;
|
|
512
565
|
await props.remove(p.id);
|
|
513
566
|
}
|
|
@@ -7,15 +7,10 @@
|
|
|
7
7
|
<q-card class="mfa-card">
|
|
8
8
|
<q-card-section class="header">
|
|
9
9
|
<q-icon name="lock" size="22px" color="amber-9" />
|
|
10
|
-
<div class="text-h6">
|
|
10
|
+
<div class="text-h6">{{ msg.mfa.title }}</div>
|
|
11
11
|
</q-card-section>
|
|
12
12
|
<q-card-section>
|
|
13
|
-
<p class="text-body2 q-mb-sm">
|
|
14
|
-
{{
|
|
15
|
-
description ??
|
|
16
|
-
'Diese Aktion ist sicherheitskritisch. Bitte 6-stelligen TOTP-Code aus Authenticator eingeben.'
|
|
17
|
-
}}
|
|
18
|
-
</p>
|
|
13
|
+
<p class="text-body2 q-mb-sm">{{ resolvedDescription }}</p>
|
|
19
14
|
<q-input
|
|
20
15
|
v-model="code"
|
|
21
16
|
autofocus
|
|
@@ -23,7 +18,7 @@
|
|
|
23
18
|
maxlength="6"
|
|
24
19
|
outlined
|
|
25
20
|
dense
|
|
26
|
-
label="
|
|
21
|
+
:label="msg.mfa.codeLabel"
|
|
27
22
|
:error="!!error"
|
|
28
23
|
:error-message="error"
|
|
29
24
|
/>
|
|
@@ -36,12 +31,12 @@
|
|
|
36
31
|
</slot>
|
|
37
32
|
</q-card-section>
|
|
38
33
|
<q-card-actions align="right">
|
|
39
|
-
<q-btn flat label="
|
|
34
|
+
<q-btn flat :label="common.cancel" v-close-popup />
|
|
40
35
|
<q-btn
|
|
41
36
|
unelevated
|
|
42
37
|
color="amber-9"
|
|
43
38
|
text-color="black"
|
|
44
|
-
label="
|
|
39
|
+
:label="common.confirm"
|
|
45
40
|
:disable="code.length !== 6"
|
|
46
41
|
@click="onConfirm"
|
|
47
42
|
/>
|
|
@@ -51,7 +46,8 @@
|
|
|
51
46
|
</template>
|
|
52
47
|
|
|
53
48
|
<script setup lang="ts">
|
|
54
|
-
import { ref, watch } from 'vue';
|
|
49
|
+
import { computed, ref, watch } from 'vue';
|
|
50
|
+
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
55
51
|
|
|
56
52
|
// Cross-cutting MFA confirmation dialog. An app-specific setup hint
|
|
57
53
|
// (e.g. "MFA setup via CLI: ...") is shown via the `setupHint` prop or
|
|
@@ -69,6 +65,10 @@ const emit = defineEmits<{
|
|
|
69
65
|
(e: 'confirm', code: string): void;
|
|
70
66
|
}>();
|
|
71
67
|
|
|
68
|
+
const msg = useSaMessages('shell');
|
|
69
|
+
const common = useSaMessages('common');
|
|
70
|
+
const resolvedDescription = computed(() => props.description ?? msg.value.mfa.defaultDescription);
|
|
71
|
+
|
|
72
72
|
const code = ref('');
|
|
73
73
|
|
|
74
74
|
watch(
|
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
size="22px"
|
|
12
12
|
:color="isDangerous ? 'negative' : 'primary'"
|
|
13
13
|
/>
|
|
14
|
-
<div class="text-h6">{{ def?.label ??
|
|
14
|
+
<div class="text-h6">{{ def?.label ?? msg.actions.confirmTitle }}</div>
|
|
15
15
|
</q-card-section>
|
|
16
16
|
|
|
17
17
|
<q-card-section v-if="def">
|
|
18
18
|
<p class="tenant-action-confirm__lead">
|
|
19
|
-
|
|
19
|
+
{{ msg.actions.leadAction }} <strong>„{{ def.label }}"</strong>
|
|
20
|
+
{{ msg.actions.leadForTenant }}
|
|
20
21
|
<strong>„{{ row?.name ?? row?.slug ?? '–' }}"</strong>.
|
|
21
22
|
</p>
|
|
22
23
|
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
gate against accidentally clicking destructive actions. -->
|
|
25
26
|
<template v-if="needsTypedSlug">
|
|
26
27
|
<p class="tenant-action-confirm__hint">
|
|
27
|
-
|
|
28
|
+
{{ msg.actions.typedSlugHint }}
|
|
28
29
|
<code>{{ row?.slug }}</code
|
|
29
30
|
>:
|
|
30
31
|
</p>
|
|
@@ -51,9 +52,7 @@
|
|
|
51
52
|
:min="dateMin"
|
|
52
53
|
:label="dateLabel"
|
|
53
54
|
:error="dateError"
|
|
54
|
-
:error-message="
|
|
55
|
-
dateError ? 'Bitte ein Datum in der Zukunft wählen.' : undefined
|
|
56
|
-
"
|
|
55
|
+
:error-message="dateError ? msg.actions.dateFutureError : undefined"
|
|
57
56
|
/>
|
|
58
57
|
</template>
|
|
59
58
|
|
|
@@ -69,22 +68,18 @@
|
|
|
69
68
|
autogrow
|
|
70
69
|
class="q-mt-md"
|
|
71
70
|
:autofocus="!needsTypedSlug && !needsDate"
|
|
72
|
-
label="
|
|
71
|
+
:label="msg.actions.reasonLabel"
|
|
73
72
|
:error="reasonError"
|
|
74
|
-
:error-message="
|
|
75
|
-
reasonError
|
|
76
|
-
? 'Bitte einen Grund mit mindestens 5 Zeichen angeben.'
|
|
77
|
-
: undefined
|
|
78
|
-
"
|
|
73
|
+
:error-message="reasonError ? msg.actions.reasonError : undefined"
|
|
79
74
|
/>
|
|
80
75
|
</q-card-section>
|
|
81
76
|
|
|
82
77
|
<q-card-actions align="right">
|
|
83
|
-
<q-btn flat label="
|
|
78
|
+
<q-btn flat :label="common.cancel" @click="onCancel" />
|
|
84
79
|
<q-btn
|
|
85
80
|
unelevated
|
|
86
81
|
:color="isDangerous ? 'negative' : 'primary'"
|
|
87
|
-
:label="def?.label ??
|
|
82
|
+
:label="def?.label ?? common.confirm"
|
|
88
83
|
:disable="!canSubmit"
|
|
89
84
|
@click="onConfirm"
|
|
90
85
|
/>
|
|
@@ -96,6 +91,8 @@
|
|
|
96
91
|
<script setup lang="ts">
|
|
97
92
|
import { computed, ref, watch } from 'vue';
|
|
98
93
|
import type { TenantActionDef, TenantDto } from '@saasicat/types';
|
|
94
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
95
|
+
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
99
96
|
|
|
100
97
|
// Platform-standard confirm dialog for tenant actions. Covers both confirm
|
|
101
98
|
// paths from the manifest in a single UI:
|
|
@@ -128,6 +125,9 @@ const emit = defineEmits<{
|
|
|
128
125
|
(e: 'cancel'): void;
|
|
129
126
|
}>();
|
|
130
127
|
|
|
128
|
+
const msg = useSaMessages('tenants');
|
|
129
|
+
const common = useSaMessages('common');
|
|
130
|
+
|
|
131
131
|
const slugInput = ref('');
|
|
132
132
|
const reasonInput = ref('');
|
|
133
133
|
const dateInput = ref('');
|
|
@@ -136,7 +136,9 @@ const reasonError = ref(false);
|
|
|
136
136
|
const dateError = ref(false);
|
|
137
137
|
|
|
138
138
|
const slugErrorMessage = computed(() =>
|
|
139
|
-
slugError.value
|
|
139
|
+
slugError.value
|
|
140
|
+
? formatMessage(msg.value.actions.slugMismatch, { slug: props.row?.slug ?? '' })
|
|
141
|
+
: undefined,
|
|
140
142
|
);
|
|
141
143
|
|
|
142
144
|
watch(
|
|
@@ -161,8 +163,8 @@ const needsDate = computed(() => props.def?.confirmType === 'date');
|
|
|
161
163
|
const dateMin = computed(() => new Date().toISOString().slice(0, 10));
|
|
162
164
|
const dateLabel = computed(() => {
|
|
163
165
|
const key = props.def?.actionKey ?? '';
|
|
164
|
-
if (key.endsWith('.extend')) return
|
|
165
|
-
return
|
|
166
|
+
if (key.endsWith('.extend')) return msg.value.actions.extendUntilLabel;
|
|
167
|
+
return common.value.date;
|
|
166
168
|
});
|
|
167
169
|
|
|
168
170
|
const isDangerous = computed(() => {
|