@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,11 +3,11 @@
|
|
|
3
3
|
<!-- Header -->
|
|
4
4
|
<div class="pr-head">
|
|
5
5
|
<div class="pr-head-text">
|
|
6
|
-
<h2 class="pr-title">
|
|
6
|
+
<h2 class="pr-title">{{ msg.review.title }}</h2>
|
|
7
7
|
<p class="pr-sub">
|
|
8
|
-
|
|
8
|
+
{{ msg.review.subtitleLead }}
|
|
9
9
|
<code class="pr-mono">{{ plan.planKey }}@v{{ version.version }}</code>
|
|
10
|
-
|
|
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>
|
|
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 ?
|
|
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 :
|
|
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">
|
|
70
|
+
<h3 class="pr-card-title">{{ msg.review.cardMasterData }}</h3>
|
|
73
71
|
<div class="pr-row">
|
|
74
|
-
<div class="pr-label">
|
|
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">
|
|
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">
|
|
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">
|
|
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 & Sichtbarkeit
|
|
93
|
-
</h3>
|
|
89
|
+
<h3 class="pr-card-title">{{ pricingCardTitle }}</h3>
|
|
94
90
|
<div class="pr-row">
|
|
95
|
-
<div class="pr-label">
|
|
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">
|
|
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">
|
|
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"
|
|
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">
|
|
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">
|
|
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">
|
|
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">
|
|
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
|
-
|
|
130
|
-
>
|
|
131
|
-
<span v-else class="pr-chip pr-chip--muted">
|
|
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">
|
|
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
|
-
|
|
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">
|
|
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
|
-
|
|
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">
|
|
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
|
-
|
|
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">
|
|
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">
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
181
|
-
Vertragsschutz 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">
|
|
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">
|
|
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>
|
|
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">
|
|
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>
|
|
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">
|
|
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
|
|
391
|
-
|
|
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:
|
|
403
|
-
{ id: 'label', 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:
|
|
429
|
+
label: msg.value.review.checkQuota,
|
|
407
430
|
ok: quotaList.value.length > 0,
|
|
408
431
|
},
|
|
409
432
|
{
|
|
410
433
|
id: 'feature',
|
|
411
|
-
label:
|
|
434
|
+
label: msg.value.review.checkFeature,
|
|
412
435
|
ok: featureList.value.length > 0,
|
|
413
436
|
},
|
|
414
437
|
{
|
|
415
438
|
id: 'validFrom',
|
|
416
|
-
label:
|
|
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
|
-
?
|
|
423
|
-
|
|
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:
|
|
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">
|
|
6
|
-
<div class="pve-col-sub">
|
|
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
|
-
|
|
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
|
-
|
|
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">
|
|
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 ||
|
|
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"
|
|
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
|
-
|
|
47
|
+
{{ yearlyAlternative }}
|
|
48
48
|
</div>
|
|
49
49
|
|
|
50
|
-
<button type="button" class="pve-prev-cta">
|
|
50
|
+
<button type="button" class="pve-prev-cta">{{ msg.catalogPreview.cta }}</button>
|
|
51
51
|
|
|
52
|
-
<div class="pve-prev-sep">
|
|
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
|
-
|
|
89
|
+
{{ msg.catalogPreview.emptyContents }}
|
|
90
90
|
</li>
|
|
91
91
|
</ul>
|
|
92
92
|
|
|
93
93
|
<div class="pve-prev-foot">
|
|
94
|
-
|
|
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">
|
|
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">
|
|
6
|
-
<div class="pve-col-sub">
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
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 }}
|
|
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
|
-
|
|
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
|
}
|