@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
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
<div class="sa-pemail">
|
|
3
3
|
<header class="sa-page-head">
|
|
4
4
|
<div>
|
|
5
|
-
<h1 class="sa-page-head__title">{{
|
|
6
|
-
<p class="sa-page-head__sub">
|
|
7
|
-
Zentraler Absender für Registrierungs- und System-Mails dieser Plattform.
|
|
8
|
-
</p>
|
|
5
|
+
<h1 class="sa-page-head__title">{{ resolvedTitle }}</h1>
|
|
6
|
+
<p class="sa-page-head__sub">{{ msg.provider.subtitle }}</p>
|
|
9
7
|
</div>
|
|
10
8
|
<div class="sa-page-head__actions">
|
|
11
9
|
<q-btn
|
|
@@ -13,10 +11,10 @@
|
|
|
13
11
|
unelevated
|
|
14
12
|
color="primary"
|
|
15
13
|
icon="add"
|
|
16
|
-
label="
|
|
14
|
+
:label="msg.sender"
|
|
17
15
|
@click="openCreate"
|
|
18
16
|
/>
|
|
19
|
-
<q-btn flat icon="refresh" label="
|
|
17
|
+
<q-btn flat icon="refresh" :label="common.reload" @click="reload" />
|
|
20
18
|
</div>
|
|
21
19
|
</header>
|
|
22
20
|
|
|
@@ -34,7 +32,9 @@
|
|
|
34
32
|
<q-td>
|
|
35
33
|
<q-badge
|
|
36
34
|
:color="row.active ? 'positive' : 'grey'"
|
|
37
|
-
:label="
|
|
35
|
+
:label="
|
|
36
|
+
row.active ? msg.provider.statusActive : msg.provider.statusInactive
|
|
37
|
+
"
|
|
38
38
|
/>
|
|
39
39
|
</q-td>
|
|
40
40
|
</template>
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
dense
|
|
46
46
|
icon="send"
|
|
47
47
|
color="primary"
|
|
48
|
-
title="
|
|
48
|
+
:title="msg.provider.sendTestMail"
|
|
49
49
|
@click="openTest(row)"
|
|
50
50
|
/>
|
|
51
51
|
<q-btn
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
dense
|
|
54
54
|
icon="edit"
|
|
55
55
|
color="grey-7"
|
|
56
|
-
title="
|
|
56
|
+
:title="common.edit"
|
|
57
57
|
@click="openEdit(row)"
|
|
58
58
|
/>
|
|
59
59
|
<q-btn
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
dense
|
|
62
62
|
icon="delete"
|
|
63
63
|
color="negative"
|
|
64
|
-
title="
|
|
64
|
+
:title="common.delete"
|
|
65
65
|
@click="onDelete(row)"
|
|
66
66
|
/>
|
|
67
67
|
</q-td>
|
|
@@ -73,41 +73,72 @@
|
|
|
73
73
|
<q-card class="sa-pemail__dialog">
|
|
74
74
|
<q-card-section>
|
|
75
75
|
<div class="text-h6">
|
|
76
|
-
{{
|
|
76
|
+
{{
|
|
77
|
+
editing ? msg.provider.dialogEditTitle : msg.provider.dialogCreateTitle
|
|
78
|
+
}}
|
|
77
79
|
</div>
|
|
78
80
|
</q-card-section>
|
|
79
81
|
<q-card-section class="q-gutter-sm">
|
|
80
|
-
<q-input v-model="form.name" outlined dense label="
|
|
81
|
-
<q-input
|
|
82
|
+
<q-input v-model="form.name" outlined dense :label="common.name" />
|
|
83
|
+
<q-input
|
|
84
|
+
v-model="form.smtpHost"
|
|
85
|
+
outlined
|
|
86
|
+
dense
|
|
87
|
+
:label="msg.provider.fieldSmtpHost"
|
|
88
|
+
/>
|
|
82
89
|
<q-input
|
|
83
90
|
v-model.number="form.smtpPort"
|
|
84
91
|
outlined
|
|
85
92
|
dense
|
|
86
93
|
type="number"
|
|
87
|
-
label="
|
|
94
|
+
:label="msg.provider.fieldSmtpPort"
|
|
95
|
+
/>
|
|
96
|
+
<q-input
|
|
97
|
+
v-model="form.smtpUser"
|
|
98
|
+
outlined
|
|
99
|
+
dense
|
|
100
|
+
:label="msg.provider.fieldSmtpUser"
|
|
88
101
|
/>
|
|
89
|
-
<q-input v-model="form.smtpUser" outlined dense label="SMTP-Benutzer" />
|
|
90
102
|
<q-input
|
|
91
103
|
v-model="form.smtpPassword"
|
|
92
104
|
outlined
|
|
93
105
|
dense
|
|
94
106
|
type="password"
|
|
95
|
-
:label="
|
|
107
|
+
:label="
|
|
108
|
+
editing
|
|
109
|
+
? msg.provider.fieldSmtpPasswordEdit
|
|
110
|
+
: msg.provider.fieldSmtpPassword
|
|
111
|
+
"
|
|
96
112
|
/>
|
|
97
113
|
<q-select
|
|
98
114
|
v-model="form.encryption"
|
|
99
115
|
outlined
|
|
100
116
|
dense
|
|
101
|
-
label="
|
|
117
|
+
:label="msg.provider.fieldEncryption"
|
|
102
118
|
:options="encryptionOptions"
|
|
103
119
|
/>
|
|
104
|
-
<q-input
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
<q-input
|
|
121
|
+
v-model="form.fromEmail"
|
|
122
|
+
outlined
|
|
123
|
+
dense
|
|
124
|
+
:label="msg.provider.fieldFromEmail"
|
|
125
|
+
/>
|
|
126
|
+
<q-input
|
|
127
|
+
v-model="form.fromName"
|
|
128
|
+
outlined
|
|
129
|
+
dense
|
|
130
|
+
:label="msg.provider.fieldFromName"
|
|
131
|
+
/>
|
|
132
|
+
<q-toggle v-if="editing" v-model="form.active" :label="common.active" />
|
|
107
133
|
</q-card-section>
|
|
108
134
|
<q-card-actions align="right">
|
|
109
|
-
<q-btn flat label="
|
|
110
|
-
<q-btn
|
|
135
|
+
<q-btn flat :label="common.cancel" @click="showForm = false" />
|
|
136
|
+
<q-btn
|
|
137
|
+
unelevated
|
|
138
|
+
color="primary"
|
|
139
|
+
:label="editing ? common.save : common.create"
|
|
140
|
+
@click="onSubmit"
|
|
141
|
+
/>
|
|
111
142
|
</q-card-actions>
|
|
112
143
|
</q-card>
|
|
113
144
|
</q-dialog>
|
|
@@ -115,22 +146,36 @@
|
|
|
115
146
|
<q-dialog v-model="showTest">
|
|
116
147
|
<q-card class="sa-pemail__dialog">
|
|
117
148
|
<q-card-section>
|
|
118
|
-
<div class="text-h6">
|
|
149
|
+
<div class="text-h6">{{ msg.provider.sendTestMail }}</div>
|
|
119
150
|
<div class="text-caption text-grey-7">{{ testTarget?.name }}</div>
|
|
120
151
|
</q-card-section>
|
|
121
152
|
<q-card-section class="q-gutter-sm">
|
|
122
|
-
<q-input
|
|
123
|
-
|
|
124
|
-
|
|
153
|
+
<q-input
|
|
154
|
+
v-model="testForm.toEmail"
|
|
155
|
+
outlined
|
|
156
|
+
dense
|
|
157
|
+
:label="msg.recipient"
|
|
158
|
+
type="email"
|
|
159
|
+
/>
|
|
160
|
+
<q-input
|
|
161
|
+
v-model="testForm.subject"
|
|
162
|
+
outlined
|
|
163
|
+
dense
|
|
164
|
+
:label="msg.provider.fieldSubject"
|
|
165
|
+
/>
|
|
166
|
+
<q-banner
|
|
167
|
+
v-if="testResult"
|
|
168
|
+
:class="testResult.success ? 'bg-green-1' : 'bg-red-1'"
|
|
169
|
+
>
|
|
125
170
|
{{ testResult.message }}
|
|
126
171
|
</q-banner>
|
|
127
172
|
</q-card-section>
|
|
128
173
|
<q-card-actions align="right">
|
|
129
|
-
<q-btn flat label="
|
|
174
|
+
<q-btn flat :label="common.close" @click="showTest = false" />
|
|
130
175
|
<q-btn
|
|
131
176
|
unelevated
|
|
132
177
|
color="primary"
|
|
133
|
-
label="
|
|
178
|
+
:label="msg.provider.send"
|
|
134
179
|
:loading="testing"
|
|
135
180
|
@click="onTest"
|
|
136
181
|
/>
|
|
@@ -151,9 +196,12 @@
|
|
|
151
196
|
</template>
|
|
152
197
|
|
|
153
198
|
<script setup lang="ts">
|
|
154
|
-
import { reactive, ref } from 'vue';
|
|
199
|
+
import { computed, reactive, ref } from 'vue';
|
|
155
200
|
import { useQuasar } from 'quasar';
|
|
201
|
+
import { useSuperAdminNotify } from '../quasar/notify.js';
|
|
156
202
|
import MfaPromptDialog from '../components/MfaPromptDialog.vue';
|
|
203
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
204
|
+
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
157
205
|
import type {
|
|
158
206
|
PlatformEmailProvider,
|
|
159
207
|
PlatformEmailWriteInput,
|
|
@@ -176,36 +224,49 @@ const props = withDefaults(
|
|
|
176
224
|
mfaCode?: string,
|
|
177
225
|
) => Promise<unknown>;
|
|
178
226
|
deleteProvider: (id: string, mfaCode?: string) => Promise<unknown>;
|
|
179
|
-
testProvider: (
|
|
227
|
+
testProvider: (
|
|
228
|
+
id: string,
|
|
229
|
+
input: PlatformEmailTestInput,
|
|
230
|
+
) => Promise<PlatformEmailTestResult>;
|
|
180
231
|
title?: string;
|
|
181
232
|
requireMfaForWrite?: boolean;
|
|
182
233
|
mfaSetupHint?: string;
|
|
183
234
|
}>(),
|
|
184
235
|
{
|
|
185
|
-
title: 'Plattform-E-Mail',
|
|
186
236
|
requireMfaForWrite: false,
|
|
187
237
|
},
|
|
188
238
|
);
|
|
189
239
|
|
|
190
240
|
const q = useQuasar();
|
|
241
|
+
const notify = useSuperAdminNotify();
|
|
242
|
+
const msg = useSaMessages('email');
|
|
243
|
+
const common = useSaMessages('common');
|
|
244
|
+
const shell = useSaMessages('shell');
|
|
191
245
|
const rows = ref<PlatformEmailProvider[]>([]);
|
|
192
246
|
const loading = ref(false);
|
|
193
247
|
|
|
248
|
+
const resolvedTitle = computed(() => props.title ?? msg.value.provider.title);
|
|
249
|
+
|
|
194
250
|
const encryptionOptions = ['NONE', 'SSL', 'TLS', 'STARTTLS'];
|
|
195
251
|
|
|
196
|
-
const columns = [
|
|
197
|
-
{ name: 'name', label:
|
|
252
|
+
const columns = computed(() => [
|
|
253
|
+
{ name: 'name', label: common.value.name, field: 'name', align: 'left' as const },
|
|
198
254
|
{
|
|
199
255
|
name: 'host',
|
|
200
|
-
label:
|
|
256
|
+
label: msg.value.provider.columnHost,
|
|
201
257
|
field: (r: PlatformEmailProvider) => `${r.smtpHost}:${r.smtpPort}`,
|
|
202
258
|
align: 'left' as const,
|
|
203
259
|
},
|
|
204
|
-
{ name: 'fromEmail', label:
|
|
205
|
-
{
|
|
206
|
-
|
|
260
|
+
{ name: 'fromEmail', label: msg.value.sender, field: 'fromEmail', align: 'left' as const },
|
|
261
|
+
{
|
|
262
|
+
name: 'encryption',
|
|
263
|
+
label: msg.value.provider.columnEncryption,
|
|
264
|
+
field: 'encryption',
|
|
265
|
+
align: 'left' as const,
|
|
266
|
+
},
|
|
267
|
+
{ name: 'active', label: common.value.status, field: 'active', align: 'left' as const },
|
|
207
268
|
{ name: 'actions', label: '', field: 'id' as never, align: 'right' as 'left' },
|
|
208
|
-
];
|
|
269
|
+
]);
|
|
209
270
|
|
|
210
271
|
const showForm = ref(false);
|
|
211
272
|
const editing = ref<PlatformEmailProvider | null>(null);
|
|
@@ -264,7 +325,7 @@ function errMsg(err: unknown): string {
|
|
|
264
325
|
return (
|
|
265
326
|
(err as { response?: { data?: { message?: string } } })?.response?.data?.message ??
|
|
266
327
|
(err as Error)?.message ??
|
|
267
|
-
|
|
328
|
+
msg.value.errorAction
|
|
268
329
|
);
|
|
269
330
|
}
|
|
270
331
|
|
|
@@ -274,7 +335,7 @@ async function reload(): Promise<void> {
|
|
|
274
335
|
rows.value = await props.loadProviders();
|
|
275
336
|
} catch (err) {
|
|
276
337
|
rows.value = [];
|
|
277
|
-
|
|
338
|
+
notify('negative', errMsg(err));
|
|
278
339
|
} finally {
|
|
279
340
|
loading.value = false;
|
|
280
341
|
}
|
|
@@ -306,13 +367,16 @@ function openEdit(row: PlatformEmailProvider): void {
|
|
|
306
367
|
}
|
|
307
368
|
|
|
308
369
|
// MFA loop: on 401 the dialog stays open and asks again.
|
|
309
|
-
async function runWrite(
|
|
370
|
+
async function runWrite(
|
|
371
|
+
label: string,
|
|
372
|
+
invoke: (code: string) => Promise<unknown>,
|
|
373
|
+
): Promise<boolean> {
|
|
310
374
|
if (!props.requireMfaForWrite) {
|
|
311
375
|
try {
|
|
312
376
|
await invoke('');
|
|
313
377
|
return true;
|
|
314
378
|
} catch (err) {
|
|
315
|
-
|
|
379
|
+
notify('negative', errMsg(err));
|
|
316
380
|
return false;
|
|
317
381
|
}
|
|
318
382
|
}
|
|
@@ -326,11 +390,11 @@ async function runWrite(label: string, invoke: (code: string) => Promise<unknown
|
|
|
326
390
|
} catch (err) {
|
|
327
391
|
const status = (err as { response?: { status?: number } })?.response?.status;
|
|
328
392
|
if (status === 401) {
|
|
329
|
-
mfaError.value =
|
|
393
|
+
mfaError.value = shell.value.mfa.invalidCode;
|
|
330
394
|
continue;
|
|
331
395
|
}
|
|
332
396
|
showMfa.value = false;
|
|
333
|
-
|
|
397
|
+
notify('negative', errMsg(err));
|
|
334
398
|
return false;
|
|
335
399
|
}
|
|
336
400
|
}
|
|
@@ -360,7 +424,9 @@ async function onSubmit(): Promise<void> {
|
|
|
360
424
|
const current = editing.value;
|
|
361
425
|
const input = buildWriteInput();
|
|
362
426
|
const ok = await runWrite(
|
|
363
|
-
current ?
|
|
427
|
+
formatMessage(current ? msg.value.provider.mfaSave : msg.value.provider.mfaCreate, {
|
|
428
|
+
name: input.name,
|
|
429
|
+
}),
|
|
364
430
|
(code) =>
|
|
365
431
|
current
|
|
366
432
|
? props.updateProvider(current.id, input, code || undefined)
|
|
@@ -368,23 +434,24 @@ async function onSubmit(): Promise<void> {
|
|
|
368
434
|
);
|
|
369
435
|
if (ok) {
|
|
370
436
|
showForm.value = false;
|
|
371
|
-
|
|
437
|
+
notify('positive', msg.value.provider.saved);
|
|
372
438
|
void reload();
|
|
373
439
|
}
|
|
374
440
|
}
|
|
375
441
|
|
|
376
442
|
function onDelete(row: PlatformEmailProvider): void {
|
|
377
443
|
q.dialog({
|
|
378
|
-
title:
|
|
379
|
-
message:
|
|
380
|
-
cancel:
|
|
381
|
-
ok: { label:
|
|
444
|
+
title: msg.value.provider.deleteDialogTitle,
|
|
445
|
+
message: formatMessage(msg.value.provider.deleteDialogMessage, { name: row.name }),
|
|
446
|
+
cancel: common.value.cancel,
|
|
447
|
+
ok: { label: common.value.delete, color: 'negative' },
|
|
382
448
|
}).onOk(async () => {
|
|
383
|
-
const ok = await runWrite(
|
|
384
|
-
|
|
449
|
+
const ok = await runWrite(
|
|
450
|
+
formatMessage(msg.value.provider.mfaDelete, { name: row.name }),
|
|
451
|
+
(code) => props.deleteProvider(row.id, code || undefined),
|
|
385
452
|
);
|
|
386
453
|
if (ok) {
|
|
387
|
-
|
|
454
|
+
notify('positive', msg.value.provider.deleted);
|
|
388
455
|
void reload();
|
|
389
456
|
}
|
|
390
457
|
});
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
flat
|
|
7
7
|
dense
|
|
8
8
|
icon="arrow_back"
|
|
9
|
-
:label="
|
|
9
|
+
:label="labels.back"
|
|
10
10
|
:to="backRoute"
|
|
11
11
|
class="sa-promo-detail__back"
|
|
12
12
|
/>
|
|
13
|
-
<h1 class="sa-page-head__title">{{ data?.promo.code ??
|
|
13
|
+
<h1 class="sa-page-head__title">{{ data?.promo.code ?? labels.promoCode }}</h1>
|
|
14
14
|
<p class="sa-page-head__sub">
|
|
15
15
|
<template v-if="data">
|
|
16
16
|
{{ data.promo.valueType }} · {{ data.promo.value }} ·
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
color="primary"
|
|
29
29
|
unelevated
|
|
30
30
|
icon="edit"
|
|
31
|
-
:label="
|
|
31
|
+
:label="labels.edit"
|
|
32
32
|
@click="openEdit"
|
|
33
33
|
/>
|
|
34
34
|
<slot name="header-actions" />
|
|
@@ -37,20 +37,20 @@
|
|
|
37
37
|
|
|
38
38
|
<div class="sa-promo-detail__body">
|
|
39
39
|
<div v-if="loading" class="sa-promo-detail__state">
|
|
40
|
-
<q-spinner size="32px" />
|
|
40
|
+
<q-spinner size="32px" /> {{ msg.detail.loading }}
|
|
41
41
|
</div>
|
|
42
|
-
<div v-else-if="!data" class="sa-promo-detail__state">{{
|
|
42
|
+
<div v-else-if="!data" class="sa-promo-detail__state">{{ labels.empty }}</div>
|
|
43
43
|
|
|
44
44
|
<template v-else>
|
|
45
45
|
<div class="sa-card sa-promo-detail__section">
|
|
46
|
-
<div class="sa-promo-detail__section-head">{{
|
|
46
|
+
<div class="sa-promo-detail__section-head">{{ labels.config }}</div>
|
|
47
47
|
<slot name="config" :promo="data.promo">
|
|
48
48
|
<pre class="sa-promo-detail__kv">{{ resolveFormatPromo(data.promo) }}</pre>
|
|
49
49
|
</slot>
|
|
50
50
|
</div>
|
|
51
51
|
|
|
52
52
|
<div class="sa-card sa-promo-detail__section">
|
|
53
|
-
<div class="sa-promo-detail__section-head">{{
|
|
53
|
+
<div class="sa-promo-detail__section-head">{{ labels.stats }}</div>
|
|
54
54
|
<slot name="stats" :stats="data.stats">
|
|
55
55
|
<pre class="sa-promo-detail__kv">{{
|
|
56
56
|
JSON.stringify(data.stats, null, 2)
|
|
@@ -60,13 +60,13 @@
|
|
|
60
60
|
|
|
61
61
|
<div class="sa-card sa-promo-detail__section">
|
|
62
62
|
<div class="sa-promo-detail__section-head">
|
|
63
|
-
{{
|
|
63
|
+
{{ labels.redemptions }} ({{ data.redemptions.length }})
|
|
64
64
|
</div>
|
|
65
65
|
<slot name="redemptions" :redemptions="data.redemptions">
|
|
66
66
|
<q-table
|
|
67
67
|
flat
|
|
68
68
|
:rows="data.redemptions"
|
|
69
|
-
:columns="redemptionsColumns ??
|
|
69
|
+
:columns="redemptionsColumns ?? defaultColumns"
|
|
70
70
|
row-key="id"
|
|
71
71
|
:pagination="{ rowsPerPage: 0 }"
|
|
72
72
|
hide-pagination
|
|
@@ -97,6 +97,7 @@ import PromoCodeEditDialog, {
|
|
|
97
97
|
type PromoCodeEditRow,
|
|
98
98
|
} from '../components/dialogs/PromoCodeEditDialog.vue';
|
|
99
99
|
import type { PromoCodePlanOption, PromoCodeUpdatePayload } from '../components/dialogs/types.js';
|
|
100
|
+
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
100
101
|
|
|
101
102
|
export interface PromoDetailData {
|
|
102
103
|
promo: Record<string, unknown> & {
|
|
@@ -109,53 +110,55 @@ export interface PromoDetailData {
|
|
|
109
110
|
redemptions: Array<Record<string, unknown> & { id: string }>;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
const props =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
113
|
+
const props = defineProps<{
|
|
114
|
+
loadDetail: () => Promise<PromoDetailData>;
|
|
115
|
+
backRoute: RouteLocationRaw;
|
|
116
|
+
backLabel?: string;
|
|
117
|
+
promoCodeLabel?: string;
|
|
118
|
+
configLabel?: string;
|
|
119
|
+
statsLabel?: string;
|
|
120
|
+
redemptionsLabel?: string;
|
|
121
|
+
emptyLabel?: string;
|
|
122
|
+
redemptionsColumns?: QTableColumn[];
|
|
123
|
+
/**
|
|
124
|
+
* Maps a status string to a Quasar color name. Default: ACTIVE→positive,
|
|
125
|
+
* PAUSED→amber-7, EXPIRED→grey, else negative.
|
|
126
|
+
*/
|
|
127
|
+
statusColor?: (status: string) => string;
|
|
128
|
+
/**
|
|
129
|
+
* Optional formatter for the Konfiguration JSON block. Default:
|
|
130
|
+
* `JSON.stringify(promo, null, 2)` — the consumer can pass a curated
|
|
131
|
+
* subset (e.g. only marketing-relevant fields).
|
|
132
|
+
*/
|
|
133
|
+
formatPromo?: (promo: Record<string, unknown>) => string;
|
|
134
|
+
/**
|
|
135
|
+
* When set: enables the "Edit" button in the header and opens the
|
|
136
|
+
* {@link PromoCodeEditDialog}. The consumer provides the app-specific
|
|
137
|
+
* PATCH function (e.g. `adminApi.promoCodes.update`). After a
|
|
138
|
+
* successful save, the detail page automatically reloads via
|
|
139
|
+
* `loadDetail()` — the consumer needs to trigger nothing further.
|
|
140
|
+
*/
|
|
141
|
+
editSubmit?: (id: string, payload: PromoCodeUpdatePayload) => Promise<void>;
|
|
142
|
+
editLabel?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Plan list for the plan picker in the edit dialog. When empty, the
|
|
145
|
+
* dialog hides the plan selection.
|
|
146
|
+
*/
|
|
147
|
+
editPlans?: readonly PromoCodePlanOption[];
|
|
148
|
+
}>();
|
|
149
|
+
|
|
150
|
+
const msg = useSaMessages('promos');
|
|
151
|
+
const common = useSaMessages('common');
|
|
152
|
+
|
|
153
|
+
const labels = computed(() => ({
|
|
154
|
+
back: props.backLabel ?? common.value.back,
|
|
155
|
+
promoCode: props.promoCodeLabel ?? msg.value.detail.promoCodeLabel,
|
|
156
|
+
config: props.configLabel ?? msg.value.detail.configLabel,
|
|
157
|
+
stats: props.statsLabel ?? msg.value.detail.statsLabel,
|
|
158
|
+
redemptions: props.redemptionsLabel ?? msg.value.detail.redemptionsLabel,
|
|
159
|
+
empty: props.emptyLabel ?? msg.value.detail.emptyLabel,
|
|
160
|
+
edit: props.editLabel ?? common.value.edit,
|
|
161
|
+
}));
|
|
159
162
|
|
|
160
163
|
const data = ref<PromoDetailData | null>(null);
|
|
161
164
|
const loading = ref(false);
|
|
@@ -243,24 +246,24 @@ function resolveFormatPromo(promo: Record<string, unknown>): string {
|
|
|
243
246
|
return JSON.stringify(promo, null, 2);
|
|
244
247
|
}
|
|
245
248
|
|
|
246
|
-
const
|
|
249
|
+
const defaultColumns = computed<QTableColumn[]>(() => [
|
|
247
250
|
{
|
|
248
251
|
name: 'tenant',
|
|
249
|
-
label:
|
|
252
|
+
label: msg.value.detail.columnTenant,
|
|
250
253
|
field: (r: unknown) =>
|
|
251
254
|
((r as Record<string, unknown>).tenant as { slug?: string } | undefined)?.slug ?? '—',
|
|
252
255
|
align: 'left',
|
|
253
256
|
},
|
|
254
|
-
{ name: 'status', label:
|
|
257
|
+
{ name: 'status', label: common.value.status, field: 'status', align: 'left' },
|
|
255
258
|
{
|
|
256
259
|
name: 'startsAt',
|
|
257
|
-
label:
|
|
260
|
+
label: msg.value.detail.columnStart,
|
|
258
261
|
field: (r: unknown) => String((r as Record<string, unknown>).startsAt ?? '').slice(0, 10),
|
|
259
262
|
align: 'left',
|
|
260
263
|
},
|
|
261
264
|
{
|
|
262
265
|
name: 'endsAt',
|
|
263
|
-
label:
|
|
266
|
+
label: msg.value.detail.columnEnd,
|
|
264
267
|
field: (r: unknown) => {
|
|
265
268
|
const v = (r as Record<string, unknown>).endsAt;
|
|
266
269
|
return v ? String(v).slice(0, 10) : '∞';
|
|
@@ -269,14 +272,14 @@ const DEFAULT_COLUMNS: QTableColumn[] = [
|
|
|
269
272
|
},
|
|
270
273
|
{
|
|
271
274
|
name: 'redeemedAt',
|
|
272
|
-
label:
|
|
275
|
+
label: msg.value.detail.columnRedeemedAt,
|
|
273
276
|
field: (r: unknown) =>
|
|
274
277
|
String((r as Record<string, unknown>).redeemedAt ?? '')
|
|
275
278
|
.slice(0, 19)
|
|
276
279
|
.replace('T', ' '),
|
|
277
280
|
align: 'left',
|
|
278
281
|
},
|
|
279
|
-
];
|
|
282
|
+
]);
|
|
280
283
|
</script>
|
|
281
284
|
|
|
282
285
|
<style scoped>
|