@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,7 +2,7 @@
|
|
|
2
2
|
<div class="sa-promo-codes">
|
|
3
3
|
<header class="sa-page-head">
|
|
4
4
|
<div>
|
|
5
|
-
<h1 class="sa-page-head__title">
|
|
5
|
+
<h1 class="sa-page-head__title">{{ msg.title }}</h1>
|
|
6
6
|
<p v-if="subtitle" class="sa-page-head__sub">{{ subtitle }}</p>
|
|
7
7
|
</div>
|
|
8
8
|
<div class="sa-page-head__actions">
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
unelevated
|
|
13
13
|
color="primary"
|
|
14
14
|
icon="add"
|
|
15
|
-
:label="
|
|
15
|
+
:label="resolvedCreateLabel"
|
|
16
16
|
@click="showCreate = true"
|
|
17
17
|
/>
|
|
18
18
|
</slot>
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
v-model="filter.search"
|
|
42
42
|
outlined
|
|
43
43
|
dense
|
|
44
|
-
label="
|
|
44
|
+
:label="msg.list.searchLabel"
|
|
45
45
|
clearable
|
|
46
46
|
@update:model-value="reload"
|
|
47
47
|
/>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
emit-value
|
|
53
53
|
map-options
|
|
54
54
|
clearable
|
|
55
|
-
label="
|
|
55
|
+
:label="common.status"
|
|
56
56
|
:options="statusOptions"
|
|
57
57
|
@update:model-value="reload"
|
|
58
58
|
/>
|
|
@@ -138,6 +138,8 @@ export function computePlanColors(
|
|
|
138
138
|
<script setup lang="ts">
|
|
139
139
|
import { computed, onMounted, reactive, ref } from 'vue';
|
|
140
140
|
import { useQuasar } from 'quasar';
|
|
141
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
142
|
+
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
141
143
|
import { useSuperAdminNotify } from '../quasar/notify.js';
|
|
142
144
|
import PromoCodeCreateDialog from '../components/dialogs/PromoCodeCreateDialog.vue';
|
|
143
145
|
import PromoCodeEditDialog, {
|
|
@@ -210,11 +212,15 @@ const props = withDefaults(
|
|
|
210
212
|
}>(),
|
|
211
213
|
{
|
|
212
214
|
statusOptions: () => ['ACTIVE', 'PAUSED', 'EXHAUSTED', 'EXPIRED'],
|
|
213
|
-
createLabel: 'Promo-Code anlegen',
|
|
214
215
|
planOptions: () => [],
|
|
215
216
|
},
|
|
216
217
|
);
|
|
217
218
|
|
|
219
|
+
const msg = useSaMessages('promos');
|
|
220
|
+
const common = useSaMessages('common');
|
|
221
|
+
const { intlLocale } = useSuperAdminI18n();
|
|
222
|
+
const resolvedCreateLabel = computed(() => props.createLabel ?? msg.value.createAction);
|
|
223
|
+
|
|
218
224
|
const q = useQuasar();
|
|
219
225
|
const notify = useSuperAdminNotify();
|
|
220
226
|
const rows = ref<PromoRow[]>([]);
|
|
@@ -267,23 +273,23 @@ const statTiles = computed<
|
|
|
267
273
|
if (c) counts[c]++;
|
|
268
274
|
}
|
|
269
275
|
return [
|
|
270
|
-
{ id: 'all', label:
|
|
276
|
+
{ id: 'all', label: common.value.all, count: rows.value.length },
|
|
271
277
|
{
|
|
272
278
|
id: 'active',
|
|
273
|
-
label:
|
|
279
|
+
label: common.value.active,
|
|
274
280
|
count: counts.active,
|
|
275
281
|
tone: 'positive',
|
|
276
|
-
hint:
|
|
282
|
+
hint: msg.value.list.hintRedeemable,
|
|
277
283
|
},
|
|
278
284
|
{
|
|
279
285
|
id: 'scheduled',
|
|
280
|
-
label:
|
|
286
|
+
label: msg.value.list.tileScheduled,
|
|
281
287
|
count: counts.scheduled,
|
|
282
288
|
tone: 'info',
|
|
283
|
-
hint:
|
|
289
|
+
hint: msg.value.list.hintStartsLater,
|
|
284
290
|
},
|
|
285
|
-
{ id: 'paused', label:
|
|
286
|
-
{ id: 'expired', label:
|
|
291
|
+
{ id: 'paused', label: msg.value.list.tilePaused, count: counts.paused, tone: 'warn' },
|
|
292
|
+
{ id: 'expired', label: common.value.expired, count: counts.expired, tone: 'muted' },
|
|
287
293
|
];
|
|
288
294
|
});
|
|
289
295
|
|
|
@@ -305,36 +311,41 @@ function onStatusTileClick(id: StatusFilter): void {
|
|
|
305
311
|
}
|
|
306
312
|
}
|
|
307
313
|
|
|
308
|
-
const baseColumns = [
|
|
314
|
+
const baseColumns = computed(() => [
|
|
309
315
|
{
|
|
310
316
|
name: 'code',
|
|
311
|
-
label:
|
|
317
|
+
label: msg.value.list.columnCode,
|
|
312
318
|
field: 'code',
|
|
313
319
|
align: 'left' as const,
|
|
314
320
|
sortable: true,
|
|
315
321
|
},
|
|
316
|
-
{
|
|
317
|
-
|
|
318
|
-
|
|
322
|
+
{
|
|
323
|
+
name: 'valueType',
|
|
324
|
+
label: common.value.type,
|
|
325
|
+
field: 'valueType',
|
|
326
|
+
align: 'left' as const,
|
|
327
|
+
},
|
|
328
|
+
{ name: 'value', label: msg.value.list.columnValue, field: 'value', align: 'right' as const },
|
|
329
|
+
{ name: 'status', label: common.value.status, field: 'status', align: 'left' as const },
|
|
319
330
|
{
|
|
320
331
|
name: 'redemptions',
|
|
321
|
-
label:
|
|
332
|
+
label: msg.value.list.columnRedemptions,
|
|
322
333
|
field: (r: PromoRow) => `${r.redemptionsCount} / ${r.maxRedemptions ?? '∞'}`,
|
|
323
334
|
align: 'right' as const,
|
|
324
335
|
},
|
|
325
336
|
{
|
|
326
337
|
name: 'campaign',
|
|
327
|
-
label:
|
|
338
|
+
label: msg.value.list.columnCampaign,
|
|
328
339
|
field: (r: PromoRow) => r.campaignTag ?? '—',
|
|
329
340
|
align: 'left' as const,
|
|
330
341
|
},
|
|
331
342
|
{
|
|
332
343
|
name: 'validUntil',
|
|
333
|
-
label:
|
|
344
|
+
label: msg.value.list.columnValidUntil,
|
|
334
345
|
field: (r: PromoRow) => formatDate(r.validUntil) ?? '—',
|
|
335
346
|
align: 'left' as const,
|
|
336
347
|
},
|
|
337
|
-
];
|
|
348
|
+
]);
|
|
338
349
|
|
|
339
350
|
// Built-in default actions — APPENDED to the consumer actions, not replacing them.
|
|
340
351
|
const bakedActions = computed<PromoRowAction[]>(() => {
|
|
@@ -342,7 +353,7 @@ const bakedActions = computed<PromoRowAction[]>(() => {
|
|
|
342
353
|
if (props.enableEdit && props.submitEdit) {
|
|
343
354
|
out.push({
|
|
344
355
|
id: 'edit',
|
|
345
|
-
label:
|
|
356
|
+
label: common.value.edit,
|
|
346
357
|
icon: 'edit',
|
|
347
358
|
color: 'primary',
|
|
348
359
|
handler: (row) => openEdit(row),
|
|
@@ -351,7 +362,7 @@ const bakedActions = computed<PromoRowAction[]>(() => {
|
|
|
351
362
|
if (props.enableStatusToggle && props.submitEdit) {
|
|
352
363
|
out.push({
|
|
353
364
|
id: 'pause',
|
|
354
|
-
label:
|
|
365
|
+
label: msg.value.list.actionPause,
|
|
355
366
|
icon: 'pause',
|
|
356
367
|
color: 'warning',
|
|
357
368
|
condition: (row) => row.status === 'ACTIVE',
|
|
@@ -359,7 +370,7 @@ const bakedActions = computed<PromoRowAction[]>(() => {
|
|
|
359
370
|
});
|
|
360
371
|
out.push({
|
|
361
372
|
id: 'activate',
|
|
362
|
-
label:
|
|
373
|
+
label: msg.value.list.actionActivate,
|
|
363
374
|
icon: 'play_arrow',
|
|
364
375
|
color: 'primary',
|
|
365
376
|
condition: (row) => row.status === 'PAUSED',
|
|
@@ -369,7 +380,7 @@ const bakedActions = computed<PromoRowAction[]>(() => {
|
|
|
369
380
|
if (props.enableDelete && props.submitDelete) {
|
|
370
381
|
out.push({
|
|
371
382
|
id: 'delete',
|
|
372
|
-
label:
|
|
383
|
+
label: common.value.delete,
|
|
373
384
|
icon: 'delete',
|
|
374
385
|
color: 'negative',
|
|
375
386
|
handler: (row) => onDeleteClick(row),
|
|
@@ -384,7 +395,7 @@ const mergedActions = computed<readonly PromoRowAction[]>(() => [
|
|
|
384
395
|
]);
|
|
385
396
|
|
|
386
397
|
const effectiveColumns = computed(() => {
|
|
387
|
-
const cols = [...baseColumns];
|
|
398
|
+
const cols = [...baseColumns.value];
|
|
388
399
|
if (mergedActions.value.length > 0) {
|
|
389
400
|
cols.push({
|
|
390
401
|
name: 'actions',
|
|
@@ -503,14 +514,14 @@ function onDeleteClick(row: PromoRow): void {
|
|
|
503
514
|
if (!props.submitDelete) return;
|
|
504
515
|
const submit = props.submitDelete;
|
|
505
516
|
q.dialog({
|
|
506
|
-
title:
|
|
507
|
-
message:
|
|
508
|
-
cancel:
|
|
509
|
-
ok: { label:
|
|
517
|
+
title: formatMessage(msg.value.list.deleteTitle, { code: row.code }),
|
|
518
|
+
message: msg.value.list.deleteMessage,
|
|
519
|
+
cancel: common.value.cancel,
|
|
520
|
+
ok: { label: common.value.delete, color: 'negative' },
|
|
510
521
|
}).onOk(async () => {
|
|
511
522
|
try {
|
|
512
523
|
await submit(row.id);
|
|
513
|
-
notify('positive',
|
|
524
|
+
notify('positive', formatMessage(msg.value.list.deletedNotice, { code: row.code }));
|
|
514
525
|
await reload();
|
|
515
526
|
} catch (err) {
|
|
516
527
|
notify('negative', errMsg(err));
|
|
@@ -519,12 +530,12 @@ function onDeleteClick(row: PromoRow): void {
|
|
|
519
530
|
}
|
|
520
531
|
|
|
521
532
|
function onCreated(): void {
|
|
522
|
-
notify('positive',
|
|
533
|
+
notify('positive', msg.value.list.createdNotice);
|
|
523
534
|
void reload();
|
|
524
535
|
}
|
|
525
536
|
|
|
526
537
|
function onUpdated(): void {
|
|
527
|
-
notify('positive',
|
|
538
|
+
notify('positive', msg.value.list.updatedNotice);
|
|
528
539
|
void reload();
|
|
529
540
|
}
|
|
530
541
|
|
|
@@ -532,14 +543,14 @@ function errMsg(err: unknown): string {
|
|
|
532
543
|
return (
|
|
533
544
|
(err as { response?: { data?: { message?: string } } })?.response?.data?.message ??
|
|
534
545
|
(err as Error)?.message ??
|
|
535
|
-
|
|
546
|
+
msg.value.list.actionFailed
|
|
536
547
|
);
|
|
537
548
|
}
|
|
538
549
|
|
|
539
550
|
function formatDate(iso: string | Date | null | undefined): string | null {
|
|
540
551
|
if (!iso) return null;
|
|
541
552
|
try {
|
|
542
|
-
return new Date(iso).toLocaleDateString(
|
|
553
|
+
return new Date(iso).toLocaleDateString(intlLocale.value, {
|
|
543
554
|
day: '2-digit',
|
|
544
555
|
month: 'short',
|
|
545
556
|
year: 'numeric',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="sa-subscriptions">
|
|
3
3
|
<header class="sa-subscriptions__head">
|
|
4
4
|
<div>
|
|
5
|
-
<h1 class="sa-subscriptions__title">
|
|
5
|
+
<h1 class="sa-subscriptions__title">{{ msg.subscriptions.title }}</h1>
|
|
6
6
|
<p v-if="subtitle" class="sa-subscriptions__sub">{{ subtitle }}</p>
|
|
7
7
|
</div>
|
|
8
8
|
<slot name="head-actions" />
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
<script setup lang="ts">
|
|
28
28
|
import { computed, onMounted, ref } from 'vue';
|
|
29
|
+
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
29
30
|
|
|
30
31
|
// Platform standard page: subscriptions (minimal). Apps pass
|
|
31
32
|
// `loadSubscriptions` + optional `columns` through — default columns show
|
|
@@ -60,39 +61,43 @@ const props = withDefaults(
|
|
|
60
61
|
{},
|
|
61
62
|
);
|
|
62
63
|
|
|
64
|
+
const msg = useSaMessages('tenants');
|
|
65
|
+
const common = useSaMessages('common');
|
|
66
|
+
const { intlLocale } = useSuperAdminI18n();
|
|
67
|
+
|
|
63
68
|
const rows = ref<SubscriptionRow[]>([]);
|
|
64
69
|
const loading = ref(false);
|
|
65
70
|
|
|
66
|
-
const defaultColumns
|
|
71
|
+
const defaultColumns = computed<Column[]>(() => [
|
|
67
72
|
{
|
|
68
73
|
name: 'tenant',
|
|
69
|
-
label:
|
|
74
|
+
label: msg.value.tenant,
|
|
70
75
|
field: (r) => r.tenant?.name ?? r.tenant?.slug ?? r.tenantSlug ?? '—',
|
|
71
76
|
align: 'left',
|
|
72
77
|
sortable: true,
|
|
73
78
|
},
|
|
74
79
|
{
|
|
75
80
|
name: 'plan',
|
|
76
|
-
label:
|
|
81
|
+
label: msg.value.plan,
|
|
77
82
|
field: (r) => r.plan ?? r.planId ?? '—',
|
|
78
83
|
align: 'left',
|
|
79
84
|
},
|
|
80
85
|
{
|
|
81
86
|
name: 'status',
|
|
82
|
-
label:
|
|
87
|
+
label: common.value.status,
|
|
83
88
|
field: (r) => r.status ?? '—',
|
|
84
89
|
align: 'left',
|
|
85
90
|
},
|
|
86
91
|
{
|
|
87
92
|
name: 'periodEndsAt',
|
|
88
|
-
label:
|
|
93
|
+
label: msg.value.subscriptions.columnEndsAt,
|
|
89
94
|
field: (r) => formatDate(r.periodEndsAt) ?? '∞',
|
|
90
95
|
align: 'left',
|
|
91
96
|
},
|
|
92
|
-
];
|
|
97
|
+
]);
|
|
93
98
|
|
|
94
99
|
const effectiveColumns = computed<Column[]>(() =>
|
|
95
|
-
props.columns ? [...props.columns] : defaultColumns,
|
|
100
|
+
props.columns ? [...props.columns] : defaultColumns.value,
|
|
96
101
|
);
|
|
97
102
|
|
|
98
103
|
async function reload() {
|
|
@@ -112,7 +117,7 @@ onMounted(reload);
|
|
|
112
117
|
function formatDate(iso: string | null | undefined): string | null {
|
|
113
118
|
if (!iso) return null;
|
|
114
119
|
try {
|
|
115
|
-
return new Date(iso).toLocaleDateString(
|
|
120
|
+
return new Date(iso).toLocaleDateString(intlLocale.value, {
|
|
116
121
|
day: '2-digit',
|
|
117
122
|
month: 'short',
|
|
118
123
|
year: 'numeric',
|
|
@@ -20,30 +20,32 @@
|
|
|
20
20
|
</div>
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
|
-
<h1 class="sa-login-title">
|
|
23
|
+
<h1 class="sa-login-title">{{ msg.login.signIn }}</h1>
|
|
24
24
|
<p v-if="subtitle" class="sa-login-subtitle">{{ subtitle }}</p>
|
|
25
25
|
|
|
26
26
|
<q-form @submit.prevent="handleSubmit" class="sa-login-form">
|
|
27
27
|
<q-input
|
|
28
28
|
v-model="form.email"
|
|
29
|
-
label="
|
|
29
|
+
:label="msg.emailLabel"
|
|
30
30
|
type="email"
|
|
31
|
+
autocomplete="username"
|
|
31
32
|
outlined
|
|
32
33
|
dense
|
|
33
34
|
autofocus
|
|
34
35
|
:disable="loading"
|
|
35
36
|
class="q-mb-sm"
|
|
36
|
-
:rules="[(v: string) => /\S+@\S+\.\S+/.test(v) ||
|
|
37
|
+
:rules="[(v: string) => /\S+@\S+\.\S+/.test(v) || msg.invalidEmail]"
|
|
37
38
|
/>
|
|
38
39
|
<q-input
|
|
39
40
|
v-model="form.password"
|
|
40
|
-
label="
|
|
41
|
+
:label="msg.passwordLabel"
|
|
41
42
|
:type="showPw ? 'text' : 'password'"
|
|
43
|
+
autocomplete="current-password"
|
|
42
44
|
outlined
|
|
43
45
|
dense
|
|
44
46
|
:disable="loading"
|
|
45
47
|
class="q-mb-sm"
|
|
46
|
-
:rules="[(v: string) => v.length > 0 ||
|
|
48
|
+
:rules="[(v: string) => v.length > 0 || msg.login.passwordRequired]"
|
|
47
49
|
>
|
|
48
50
|
<template #append>
|
|
49
51
|
<q-icon
|
|
@@ -62,7 +64,7 @@
|
|
|
62
64
|
unelevated
|
|
63
65
|
color="primary"
|
|
64
66
|
icon="login"
|
|
65
|
-
label="
|
|
67
|
+
:label="msg.login.signIn"
|
|
66
68
|
:loading="loading"
|
|
67
69
|
:disable="!canSubmit"
|
|
68
70
|
class="full-width q-mt-sm"
|
|
@@ -71,7 +73,8 @@
|
|
|
71
73
|
</q-form>
|
|
72
74
|
|
|
73
75
|
<div v-if="devHint" class="sa-login-hint">
|
|
74
|
-
|
|
76
|
+
{{ msg.login.devHintLabel }} <code>{{ devHint.email }}</code> /
|
|
77
|
+
<code>{{ devHint.password }}</code>
|
|
75
78
|
</div>
|
|
76
79
|
</div>
|
|
77
80
|
|
|
@@ -94,6 +97,7 @@ import {
|
|
|
94
97
|
useSuperAdminLoginAdapter,
|
|
95
98
|
} from '../vue/use-super-admin-context.js';
|
|
96
99
|
import { getJson } from '../client/http-json.js';
|
|
100
|
+
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
97
101
|
import SuperAdminSetupWizard from './SuperAdminSetupWizard.vue';
|
|
98
102
|
|
|
99
103
|
interface Props {
|
|
@@ -107,6 +111,7 @@ interface Props {
|
|
|
107
111
|
const props = defineProps<Props>();
|
|
108
112
|
|
|
109
113
|
const router = useRouter();
|
|
114
|
+
const msg = useSaMessages('shell');
|
|
110
115
|
const brand = useSuperAdminBrand();
|
|
111
116
|
const endpoints = useSuperAdminEndpoints();
|
|
112
117
|
const adapter = useSuperAdminLoginAdapter();
|
|
@@ -161,11 +166,9 @@ function describeError(
|
|
|
161
166
|
result: Extract<Awaited<ReturnType<typeof adapter.login>>, { ok: false }>,
|
|
162
167
|
): string {
|
|
163
168
|
if (result.message) return result.message;
|
|
164
|
-
if (result.code === 'BAD_CREDENTIALS') return
|
|
165
|
-
if (result.code === 'NOT_SUPER_ADMIN')
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
return 'Anmeldung fehlgeschlagen.';
|
|
169
|
+
if (result.code === 'BAD_CREDENTIALS') return msg.value.login.errorBadCredentials;
|
|
170
|
+
if (result.code === 'NOT_SUPER_ADMIN') return msg.value.login.errorNotSuperAdmin;
|
|
171
|
+
return msg.value.login.errorFailed;
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
async function handleSubmit(): Promise<void> {
|
|
@@ -182,7 +185,7 @@ async function handleSubmit(): Promise<void> {
|
|
|
182
185
|
} catch (err) {
|
|
183
186
|
const e = err as { response?: { data?: { message?: string; code?: string } } };
|
|
184
187
|
errorMessage.value =
|
|
185
|
-
e.response?.data?.message ?? e.response?.data?.code ??
|
|
188
|
+
e.response?.data?.message ?? e.response?.data?.code ?? msg.value.login.errorFailed;
|
|
186
189
|
} finally {
|
|
187
190
|
loading.value = false;
|
|
188
191
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<div class="sa-setup-head">
|
|
5
5
|
<div class="sa-setup-badge">{{ iconText }}</div>
|
|
6
6
|
<div>
|
|
7
|
-
<div class="sa-setup-title">
|
|
8
|
-
<div class="sa-setup-sub">{{
|
|
7
|
+
<div class="sa-setup-title">{{ msg.setup.title }}</div>
|
|
8
|
+
<div class="sa-setup-sub">{{ subtitleText }}</div>
|
|
9
9
|
</div>
|
|
10
10
|
</div>
|
|
11
11
|
|
|
@@ -16,40 +16,44 @@
|
|
|
16
16
|
<!-- Step 1: create SuperAdmin -->
|
|
17
17
|
<q-form v-if="step === 'form'" @submit.prevent="submitCreate" class="sa-setup-form">
|
|
18
18
|
<p class="sa-setup-hint">
|
|
19
|
-
|
|
20
|
-
<code>SETUP_TOKEN</code>
|
|
19
|
+
{{ msg.setup.tokenHintBefore }}
|
|
20
|
+
<code>SETUP_TOKEN</code>
|
|
21
|
+
{{ msg.setup.tokenHintAfter }}
|
|
21
22
|
</p>
|
|
22
23
|
<q-input
|
|
23
24
|
v-model="form.token"
|
|
24
|
-
label="
|
|
25
|
+
:label="msg.setup.tokenLabel"
|
|
25
26
|
type="password"
|
|
26
27
|
outlined
|
|
27
28
|
dense
|
|
28
29
|
autofocus
|
|
29
30
|
:disable="loading"
|
|
30
31
|
class="q-mb-sm"
|
|
31
|
-
:rules="[(v: string) => v.length > 0 ||
|
|
32
|
+
:rules="[(v: string) => v.length > 0 || msg.setup.tokenRequired]"
|
|
32
33
|
/>
|
|
33
34
|
<q-input
|
|
34
35
|
v-model="form.email"
|
|
35
|
-
label="
|
|
36
|
+
:label="msg.emailLabel"
|
|
36
37
|
type="email"
|
|
37
38
|
outlined
|
|
38
39
|
dense
|
|
39
40
|
:disable="loading"
|
|
40
41
|
class="q-mb-sm"
|
|
41
|
-
:rules="[(v: string) => /\S+@\S+\.\S+/.test(v) ||
|
|
42
|
+
:rules="[(v: string) => /\S+@\S+\.\S+/.test(v) || msg.invalidEmail]"
|
|
42
43
|
/>
|
|
43
44
|
<q-input
|
|
44
45
|
v-model="form.password"
|
|
45
|
-
label="
|
|
46
|
+
:label="msg.setup.passwordLabel"
|
|
46
47
|
:type="showPw ? 'text' : 'password'"
|
|
47
48
|
outlined
|
|
48
49
|
dense
|
|
49
50
|
:disable="loading"
|
|
50
|
-
hint="
|
|
51
|
+
:hint="msg.setup.passwordHint"
|
|
51
52
|
class="q-mb-sm"
|
|
52
|
-
:rules="[
|
|
53
|
+
:rules="[
|
|
54
|
+
(v: string) =>
|
|
55
|
+
v.length === 0 || v.length >= 8 || msg.setup.passwordMinLength,
|
|
56
|
+
]"
|
|
53
57
|
>
|
|
54
58
|
<template #append>
|
|
55
59
|
<q-icon
|
|
@@ -63,7 +67,7 @@
|
|
|
63
67
|
unelevated
|
|
64
68
|
color="primary"
|
|
65
69
|
icon="person_add"
|
|
66
|
-
label="
|
|
70
|
+
:label="msg.setup.submit"
|
|
67
71
|
:loading="loading"
|
|
68
72
|
:disable="!canCreate"
|
|
69
73
|
class="full-width q-mt-sm"
|
|
@@ -74,48 +78,48 @@
|
|
|
74
78
|
<!-- Step 2: set up MFA -->
|
|
75
79
|
<div v-else-if="step === 'mfa' && result" class="sa-setup-mfa">
|
|
76
80
|
<p class="sa-setup-hint">
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
{{ msg.setup.mfaIntroBefore }} <strong>{{ result.email }}</strong>
|
|
82
|
+
{{ msg.setup.mfaIntroAfter }}
|
|
79
83
|
</p>
|
|
80
84
|
|
|
81
85
|
<div v-if="result.generatedPassword" class="sa-setup-secret">
|
|
82
|
-
<div class="sa-setup-secret__label">
|
|
86
|
+
<div class="sa-setup-secret__label">
|
|
87
|
+
{{ msg.setup.generatedPasswordLabel }}
|
|
88
|
+
</div>
|
|
83
89
|
<code class="sa-setup-secret__value">{{ result.generatedPassword }}</code>
|
|
84
90
|
</div>
|
|
85
91
|
|
|
86
92
|
<div v-if="result.qrDataUrl" class="sa-setup-qr">
|
|
87
|
-
<img :src="result.qrDataUrl" alt="
|
|
88
|
-
<div class="sa-setup-qr__hint">
|
|
93
|
+
<img :src="result.qrDataUrl" :alt="msg.setup.qrAlt" width="200" height="200" />
|
|
94
|
+
<div class="sa-setup-qr__hint">{{ msg.setup.qrHint }}</div>
|
|
89
95
|
</div>
|
|
90
96
|
|
|
91
97
|
<div class="sa-setup-secret">
|
|
92
|
-
<div class="sa-setup-secret__label">
|
|
93
|
-
In Authenticator-App als „Schlüssel manuell eingeben"
|
|
94
|
-
</div>
|
|
98
|
+
<div class="sa-setup-secret__label">{{ msg.setup.manualSecretLabel }}</div>
|
|
95
99
|
<code class="sa-setup-secret__value">{{ result.secret }}</code>
|
|
96
100
|
</div>
|
|
97
101
|
<details class="sa-setup-uri">
|
|
98
|
-
<summary>
|
|
102
|
+
<summary>{{ msg.setup.otpauthSummary }}</summary>
|
|
99
103
|
<code class="sa-setup-uri__value">{{ result.otpauthUri }}</code>
|
|
100
104
|
</details>
|
|
101
105
|
|
|
102
106
|
<q-form @submit.prevent="submitConfirm" class="sa-setup-form q-mt-md">
|
|
103
107
|
<q-input
|
|
104
108
|
v-model="mfaCode"
|
|
105
|
-
label="
|
|
109
|
+
:label="msg.setup.codeLabel"
|
|
106
110
|
outlined
|
|
107
111
|
dense
|
|
108
112
|
autofocus
|
|
109
113
|
inputmode="numeric"
|
|
110
114
|
maxlength="6"
|
|
111
115
|
:disable="loading"
|
|
112
|
-
:rules="[(v: string) => /^\d{6}$/.test(v) ||
|
|
116
|
+
:rules="[(v: string) => /^\d{6}$/.test(v) || msg.setup.codeInvalid]"
|
|
113
117
|
/>
|
|
114
118
|
<q-btn
|
|
115
119
|
unelevated
|
|
116
120
|
color="primary"
|
|
117
121
|
icon="verified_user"
|
|
118
|
-
label="
|
|
122
|
+
:label="msg.setup.confirmMfa"
|
|
119
123
|
:loading="loading"
|
|
120
124
|
:disable="!/^\d{6}$/.test(mfaCode) || loading"
|
|
121
125
|
class="full-width q-mt-sm"
|
|
@@ -123,21 +127,19 @@
|
|
|
123
127
|
/>
|
|
124
128
|
</q-form>
|
|
125
129
|
<button class="sa-setup-skip" type="button" @click="step = 'done'">
|
|
126
|
-
|
|
130
|
+
{{ msg.setup.skip }}
|
|
127
131
|
</button>
|
|
128
132
|
</div>
|
|
129
133
|
|
|
130
134
|
<!-- Step 3: done -->
|
|
131
135
|
<div v-else-if="step === 'done'" class="sa-setup-done">
|
|
132
136
|
<q-icon name="check_circle" color="positive" size="48px" />
|
|
133
|
-
<p class="sa-setup-hint">
|
|
134
|
-
Einrichtung abgeschlossen. Melde dich jetzt mit dem neuen SuperAdmin an.
|
|
135
|
-
</p>
|
|
137
|
+
<p class="sa-setup-hint">{{ msg.setup.doneHint }}</p>
|
|
136
138
|
<q-btn
|
|
137
139
|
unelevated
|
|
138
140
|
color="primary"
|
|
139
141
|
icon="login"
|
|
140
|
-
label="
|
|
142
|
+
:label="msg.setup.toLogin"
|
|
141
143
|
class="full-width"
|
|
142
144
|
@click="emit('done')"
|
|
143
145
|
/>
|
|
@@ -148,11 +150,7 @@
|
|
|
148
150
|
|
|
149
151
|
<script setup lang="ts">
|
|
150
152
|
import { computed, reactive, ref } from 'vue';
|
|
151
|
-
import {
|
|
152
|
-
SETUP_ERROR_CODES,
|
|
153
|
-
type SetupConfirmMfaResponse,
|
|
154
|
-
type SetupResult,
|
|
155
|
-
} from '@saasicat/types';
|
|
153
|
+
import { SETUP_ERROR_CODES, type SetupConfirmMfaResponse, type SetupResult } from '@saasicat/types';
|
|
156
154
|
|
|
157
155
|
import {
|
|
158
156
|
useSuperAdminBrand,
|
|
@@ -160,6 +158,8 @@ import {
|
|
|
160
158
|
useSuperAdminHttp,
|
|
161
159
|
} from '../vue/use-super-admin-context.js';
|
|
162
160
|
import { HttpJsonError, postJson as httpPostJson } from '../client/http-json.js';
|
|
161
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
162
|
+
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
163
163
|
|
|
164
164
|
interface Props {
|
|
165
165
|
/** Display name + badge abbreviation (override the app branding, e.g. from PublicBoot). */
|
|
@@ -172,12 +172,16 @@ const props = defineProps<Props>();
|
|
|
172
172
|
// shown again — the freshly created SuperAdmin then signs in.
|
|
173
173
|
const emit = defineEmits<{ done: [] }>();
|
|
174
174
|
|
|
175
|
+
const msg = useSaMessages('shell');
|
|
175
176
|
const brand = useSuperAdminBrand();
|
|
176
177
|
const endpoints = useSuperAdminEndpoints();
|
|
177
178
|
const http = useSuperAdminHttp();
|
|
178
179
|
|
|
179
180
|
const brandName = computed(() => props.displayName ?? brand.name);
|
|
180
181
|
const iconText = computed(() => props.icon ?? brand.logoText);
|
|
182
|
+
const subtitleText = computed(() =>
|
|
183
|
+
formatMessage(msg.value.setup.subtitle, { brand: brandName.value }),
|
|
184
|
+
);
|
|
181
185
|
|
|
182
186
|
const step = ref<'form' | 'mfa' | 'done'>('form');
|
|
183
187
|
const form = reactive({ token: '', email: '', password: '' });
|
|
@@ -195,15 +199,13 @@ const canCreate = computed(
|
|
|
195
199
|
!loading.value,
|
|
196
200
|
);
|
|
197
201
|
|
|
198
|
-
const
|
|
199
|
-
[SETUP_ERROR_CODES.SETUP_DISABLED]:
|
|
200
|
-
[SETUP_ERROR_CODES.INVALID_SETUP_TOKEN]:
|
|
201
|
-
[SETUP_ERROR_CODES.SETUP_ALREADY_DONE]:
|
|
202
|
-
|
|
203
|
-
[SETUP_ERROR_CODES.
|
|
204
|
-
|
|
205
|
-
'Diese E-Mail ist bereits vergeben. Wähle eine andere oder hebe den bestehenden User per CLI/DB zum SUPER_ADMIN an.',
|
|
206
|
-
};
|
|
202
|
+
const errorByCode = computed<Record<string, string>>(() => ({
|
|
203
|
+
[SETUP_ERROR_CODES.SETUP_DISABLED]: msg.value.setup.errorSetupDisabled,
|
|
204
|
+
[SETUP_ERROR_CODES.INVALID_SETUP_TOKEN]: msg.value.setup.errorInvalidToken,
|
|
205
|
+
[SETUP_ERROR_CODES.SETUP_ALREADY_DONE]: msg.value.setup.errorAlreadyDone,
|
|
206
|
+
[SETUP_ERROR_CODES.INVALID_EMAIL]: msg.value.setup.errorInvalidEmail,
|
|
207
|
+
[SETUP_ERROR_CODES.EMAIL_EXISTS]: msg.value.setup.errorEmailExists,
|
|
208
|
+
}));
|
|
207
209
|
|
|
208
210
|
// Goes through the injected HttpClient (consumer's auth/baseURL apply);
|
|
209
211
|
// maps the error code from the body to a readable message.
|
|
@@ -213,7 +215,8 @@ async function postJson<T>(path: string, body: unknown): Promise<T> {
|
|
|
213
215
|
} catch (err) {
|
|
214
216
|
if (err instanceof HttpJsonError) {
|
|
215
217
|
throw new Error(
|
|
216
|
-
(err.code &&
|
|
218
|
+
(err.code && errorByCode.value[err.code]) ||
|
|
219
|
+
formatMessage(msg.value.setup.errorHttp, { status: err.status }),
|
|
217
220
|
);
|
|
218
221
|
}
|
|
219
222
|
throw err;
|
|
@@ -232,7 +235,7 @@ async function submitCreate(): Promise<void> {
|
|
|
232
235
|
});
|
|
233
236
|
step.value = 'mfa';
|
|
234
237
|
} catch (err) {
|
|
235
|
-
errorMessage.value = err instanceof Error ? err.message :
|
|
238
|
+
errorMessage.value = err instanceof Error ? err.message : msg.value.setup.errorFailed;
|
|
236
239
|
} finally {
|
|
237
240
|
loading.value = false;
|
|
238
241
|
}
|
|
@@ -251,10 +254,11 @@ async function submitConfirm(): Promise<void> {
|
|
|
251
254
|
if (res.ok) {
|
|
252
255
|
step.value = 'done';
|
|
253
256
|
} else {
|
|
254
|
-
errorMessage.value =
|
|
257
|
+
errorMessage.value = msg.value.setup.errorCodeInvalid;
|
|
255
258
|
}
|
|
256
259
|
} catch (err) {
|
|
257
|
-
errorMessage.value =
|
|
260
|
+
errorMessage.value =
|
|
261
|
+
err instanceof Error ? err.message : msg.value.setup.errorConfirmFailed;
|
|
258
262
|
} finally {
|
|
259
263
|
loading.value = false;
|
|
260
264
|
}
|