@saasicat/ui-vue 0.5.0 → 0.7.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 +17 -0
- package/dist/{chunk-EH7TOD4H.js → chunk-A3W2N26K.js} +73 -77
- package/dist/{chunk-3YP2ZAOD.js → chunk-QZOSDCNP.js} +36 -1
- package/dist/chunk-X5SQ5HMB.js +3231 -0
- package/dist/client/index.cjs +3279 -76
- package/dist/client/index.d.cts +31 -5
- package/dist/client/index.d.ts +31 -5
- package/dist/client/index.js +23 -5
- package/dist/index.cjs +3709 -465
- package/dist/index.d.cts +37 -54
- package/dist/index.d.ts +37 -54
- package/dist/index.js +347 -311
- package/dist/messages-7b0P8W75.d.cts +1652 -0
- package/dist/messages-7b0P8W75.d.ts +1652 -0
- package/dist/quasar/index.cjs +3185 -4
- package/dist/quasar/index.d.cts +11 -2
- package/dist/quasar/index.d.ts +11 -2
- package/dist/quasar/index.js +8 -3
- package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
- package/dist/testing-e2e/admin-pages-suite.js +14 -11
- package/dist/{ui-notify-COUzXEQ4.d.ts → use-super-admin-i18n-B3v3-SWZ.d.ts} +35 -3
- package/dist/{ui-notify-D_eAAwRh.d.cts → use-super-admin-i18n-DMktRVEt.d.cts} +35 -3
- package/package.json +2 -2
- package/src/client/action-registry.ts +4 -4
- package/src/client/batch-column-fetcher.ts +4 -4
- package/src/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 +2 -0
- package/src/client/manifest-loader.ts +4 -4
- package/src/client/nav-builder.ts +44 -51
- package/src/client/plan-versions-catalog.ts +42 -30
- package/src/components/BundleVersionPublishDialog.vue +35 -24
- 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 +20 -9
- 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 +1 -1
- package/src/pages-standard/AdminLayout.vue +42 -17
- 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 +29 -15
- package/src/pages-standard/DiscoveryPage.vue +40 -28
- package/src/pages-standard/EmailHistoryPage.vue +92 -59
- package/src/pages-standard/MarketingCatalogPage.vue +35 -16
- package/src/pages-standard/PilotsPage.vue +57 -41
- package/src/pages-standard/PlanVersionsPage.vue +7 -3
- package/src/pages-standard/PlansPage.vue +89 -57
- package/src/pages-standard/PlatformEmailPage.vue +115 -50
- package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
- package/src/pages-standard/PromoCodesPage.vue +46 -35
- package/src/pages-standard/SubscriptionsPage.vue +14 -9
- package/src/pages-standard/SuperAdminLoginPage.vue +16 -13
- package/src/pages-standard/SuperAdminSetupWizard.vue +51 -47
- package/src/pages-standard/TenantDetailPage.vue +50 -36
- package/src/pages-standard/TenantsPage.vue +54 -41
- package/src/pages-standard/UsersPage.vue +66 -42
- 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 +41 -25
- package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +45 -26
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +7 -5
- package/src/pages-standard/plan-versions/PlanVersionsList.vue +39 -16
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +24 -12
- 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 +72 -37
- package/src/pages-tenant/PlanChangeWizard.vue +8 -4
- package/src/pages-tenant/TenantPlanSection.vue +17 -13
- package/src/pages-tenant/default-i18n.ts +217 -1
- package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +8 -6
- package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +9 -10
- package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
- package/src/quasar/create-super-admin-app.ts +19 -0
- package/src/testing-e2e/admin-pages-suite.ts +14 -11
- package/src/vue/project-page-host.ts +5 -3
- package/src/vue/use-audit-entries.ts +3 -3
- package/src/vue/use-bulk-publish.ts +6 -4
- package/src/vue/use-bundle-versions-map.ts +6 -4
- package/src/vue/use-bundles.ts +13 -13
- package/src/vue/use-catalog-entries.ts +13 -10
- package/src/vue/use-discovery.ts +8 -5
- package/src/vue/use-entitlement.ts +3 -3
- package/src/vue/use-live-plan-versions.ts +4 -4
- package/src/vue/use-marketing-projections.ts +9 -6
- package/src/vue/use-nav.ts +8 -2
- package/src/vue/use-plan-editor.ts +2 -2
- package/src/vue/use-plan-versions.ts +3 -3
- package/src/vue/use-plans.ts +12 -12
- package/src/vue/use-platform-tenant-actions.ts +9 -6
- package/src/vue/use-promotions.ts +9 -10
- package/src/vue/use-subscription-draft.ts +2 -3
- package/src/vue/use-super-admin-context.ts +3 -3
- package/src/vue/use-super-admin-i18n.ts +82 -0
- package/src/vue/use-tenant-action-flow.ts +11 -6
- package/src/vue/use-tenant-manifest.ts +1 -3
- package/src/vue/use-tenant-subscription-bundles.ts +4 -4
- package/src/vue/use-tenants.ts +3 -3
- package/dist/action-registry-CQ2wL3R-.d.cts +0 -85
- package/dist/action-registry-CQ2wL3R-.d.ts +0 -85
- package/dist/chunk-LVGUSD3T.js +0 -26
- 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/vue/use-business-types.ts +0 -269
|
@@ -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,10 +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';
|
|
156
201
|
import { useSuperAdminNotify } from '../quasar/notify.js';
|
|
157
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';
|
|
158
205
|
import type {
|
|
159
206
|
PlatformEmailProvider,
|
|
160
207
|
PlatformEmailWriteInput,
|
|
@@ -177,37 +224,49 @@ const props = withDefaults(
|
|
|
177
224
|
mfaCode?: string,
|
|
178
225
|
) => Promise<unknown>;
|
|
179
226
|
deleteProvider: (id: string, mfaCode?: string) => Promise<unknown>;
|
|
180
|
-
testProvider: (
|
|
227
|
+
testProvider: (
|
|
228
|
+
id: string,
|
|
229
|
+
input: PlatformEmailTestInput,
|
|
230
|
+
) => Promise<PlatformEmailTestResult>;
|
|
181
231
|
title?: string;
|
|
182
232
|
requireMfaForWrite?: boolean;
|
|
183
233
|
mfaSetupHint?: string;
|
|
184
234
|
}>(),
|
|
185
235
|
{
|
|
186
|
-
title: 'Plattform-E-Mail',
|
|
187
236
|
requireMfaForWrite: false,
|
|
188
237
|
},
|
|
189
238
|
);
|
|
190
239
|
|
|
191
240
|
const q = useQuasar();
|
|
192
241
|
const notify = useSuperAdminNotify();
|
|
242
|
+
const msg = useSaMessages('email');
|
|
243
|
+
const common = useSaMessages('common');
|
|
244
|
+
const shell = useSaMessages('shell');
|
|
193
245
|
const rows = ref<PlatformEmailProvider[]>([]);
|
|
194
246
|
const loading = ref(false);
|
|
195
247
|
|
|
248
|
+
const resolvedTitle = computed(() => props.title ?? msg.value.provider.title);
|
|
249
|
+
|
|
196
250
|
const encryptionOptions = ['NONE', 'SSL', 'TLS', 'STARTTLS'];
|
|
197
251
|
|
|
198
|
-
const columns = [
|
|
199
|
-
{ name: 'name', label:
|
|
252
|
+
const columns = computed(() => [
|
|
253
|
+
{ name: 'name', label: common.value.name, field: 'name', align: 'left' as const },
|
|
200
254
|
{
|
|
201
255
|
name: 'host',
|
|
202
|
-
label:
|
|
256
|
+
label: msg.value.provider.columnHost,
|
|
203
257
|
field: (r: PlatformEmailProvider) => `${r.smtpHost}:${r.smtpPort}`,
|
|
204
258
|
align: 'left' as const,
|
|
205
259
|
},
|
|
206
|
-
{ name: 'fromEmail', label:
|
|
207
|
-
{
|
|
208
|
-
|
|
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 },
|
|
209
268
|
{ name: 'actions', label: '', field: 'id' as never, align: 'right' as 'left' },
|
|
210
|
-
];
|
|
269
|
+
]);
|
|
211
270
|
|
|
212
271
|
const showForm = ref(false);
|
|
213
272
|
const editing = ref<PlatformEmailProvider | null>(null);
|
|
@@ -266,7 +325,7 @@ function errMsg(err: unknown): string {
|
|
|
266
325
|
return (
|
|
267
326
|
(err as { response?: { data?: { message?: string } } })?.response?.data?.message ??
|
|
268
327
|
(err as Error)?.message ??
|
|
269
|
-
|
|
328
|
+
msg.value.errorAction
|
|
270
329
|
);
|
|
271
330
|
}
|
|
272
331
|
|
|
@@ -308,7 +367,10 @@ function openEdit(row: PlatformEmailProvider): void {
|
|
|
308
367
|
}
|
|
309
368
|
|
|
310
369
|
// MFA loop: on 401 the dialog stays open and asks again.
|
|
311
|
-
async function runWrite(
|
|
370
|
+
async function runWrite(
|
|
371
|
+
label: string,
|
|
372
|
+
invoke: (code: string) => Promise<unknown>,
|
|
373
|
+
): Promise<boolean> {
|
|
312
374
|
if (!props.requireMfaForWrite) {
|
|
313
375
|
try {
|
|
314
376
|
await invoke('');
|
|
@@ -328,7 +390,7 @@ async function runWrite(label: string, invoke: (code: string) => Promise<unknown
|
|
|
328
390
|
} catch (err) {
|
|
329
391
|
const status = (err as { response?: { status?: number } })?.response?.status;
|
|
330
392
|
if (status === 401) {
|
|
331
|
-
mfaError.value =
|
|
393
|
+
mfaError.value = shell.value.mfa.invalidCode;
|
|
332
394
|
continue;
|
|
333
395
|
}
|
|
334
396
|
showMfa.value = false;
|
|
@@ -362,7 +424,9 @@ async function onSubmit(): Promise<void> {
|
|
|
362
424
|
const current = editing.value;
|
|
363
425
|
const input = buildWriteInput();
|
|
364
426
|
const ok = await runWrite(
|
|
365
|
-
current ?
|
|
427
|
+
formatMessage(current ? msg.value.provider.mfaSave : msg.value.provider.mfaCreate, {
|
|
428
|
+
name: input.name,
|
|
429
|
+
}),
|
|
366
430
|
(code) =>
|
|
367
431
|
current
|
|
368
432
|
? props.updateProvider(current.id, input, code || undefined)
|
|
@@ -370,23 +434,24 @@ async function onSubmit(): Promise<void> {
|
|
|
370
434
|
);
|
|
371
435
|
if (ok) {
|
|
372
436
|
showForm.value = false;
|
|
373
|
-
notify('positive',
|
|
437
|
+
notify('positive', msg.value.provider.saved);
|
|
374
438
|
void reload();
|
|
375
439
|
}
|
|
376
440
|
}
|
|
377
441
|
|
|
378
442
|
function onDelete(row: PlatformEmailProvider): void {
|
|
379
443
|
q.dialog({
|
|
380
|
-
title:
|
|
381
|
-
message:
|
|
382
|
-
cancel:
|
|
383
|
-
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' },
|
|
384
448
|
}).onOk(async () => {
|
|
385
|
-
const ok = await runWrite(
|
|
386
|
-
|
|
449
|
+
const ok = await runWrite(
|
|
450
|
+
formatMessage(msg.value.provider.mfaDelete, { name: row.name }),
|
|
451
|
+
(code) => props.deleteProvider(row.id, code || undefined),
|
|
387
452
|
);
|
|
388
453
|
if (ok) {
|
|
389
|
-
notify('positive',
|
|
454
|
+
notify('positive', msg.value.provider.deleted);
|
|
390
455
|
void reload();
|
|
391
456
|
}
|
|
392
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>
|