@saasicat/ui-vue 0.19.0 → 0.22.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/dist/{catalog-JXdKdIqG.d.cts → catalog-DJcfm5N2.d.cts} +21 -7
- package/dist/{catalog-JXdKdIqG.d.ts → catalog-DJcfm5N2.d.ts} +21 -7
- package/dist/{chunk-DPTIOQXX.js → chunk-F7NRM6KI.js} +56 -28
- package/dist/{chunk-RVG6LQHL.js → chunk-LY52T4N4.js} +21 -2
- package/dist/{chunk-ORX3ZG47.js → chunk-XMMFN2WJ.js} +74 -3
- package/dist/client/index.cjs +134 -29
- package/dist/client/index.d.cts +53 -3
- package/dist/client/index.d.ts +53 -3
- package/dist/client/index.js +14 -2
- package/dist/index.cjs +321 -162
- package/dist/index.d.cts +30 -8
- package/dist/index.d.ts +30 -8
- package/dist/index.js +176 -133
- package/dist/quasar/index.cjs +78 -29
- package/dist/quasar/index.d.cts +2 -2
- package/dist/quasar/index.d.ts +2 -2
- package/dist/quasar/index.js +6 -2
- package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
- package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
- package/dist/{use-super-admin-i18n-BlqcD0N7.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
- package/dist/{use-super-admin-i18n-1nCOkvgi.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
- package/package.json +18 -8
- package/src/client/i18n/messages/bundles.ts +0 -2
- package/src/client/i18n/messages/common.ts +24 -0
- package/src/client/i18n/messages/discovery.ts +2 -2
- package/src/client/i18n/messages/marketing.ts +2 -4
- package/src/client/i18n/messages/plan-detail.ts +0 -4
- package/src/client/i18n/messages/plans.ts +20 -8
- package/src/client/i18n/messages/tenants.ts +0 -2
- package/src/client/index.ts +1 -0
- package/src/client/manifest-loader.ts +1 -1
- package/src/client/resolve-plans.ts +144 -0
- package/src/components/BundleVersionPublishDialog.vue +6 -6
- package/src/components/KvBlock.vue +4 -4
- package/src/components/MarketingPromotionsTab.vue +34 -48
- package/src/components/MfaPromptDialog.vue +8 -2
- package/src/components/TenantActionConfirmDialog.vue +4 -4
- package/src/components/VersionDiffPreview.vue +7 -7
- package/src/components/admin-page/AdminBody.vue +40 -0
- package/src/components/admin-page/AdminFilters.vue +12 -0
- package/src/components/admin-page/AdminHero.vue +49 -0
- package/src/components/admin-page/AdminKpi.vue +84 -0
- package/src/components/admin-page/AdminPage.vue +16 -0
- package/src/components/admin-page/AdminPaginator.vue +195 -0
- package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
- package/src/components/admin-page/AdminSection.vue +53 -0
- package/src/components/admin-page/AdminStatistics.vue +40 -0
- package/src/components/admin-page/AdminTable.vue +175 -0
- package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
- package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
- package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
- package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
- package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
- package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
- package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
- package/src/components/dialogs/PilotCreateDialog.vue +12 -12
- package/src/components/dialogs/PilotEditDialog.vue +7 -7
- package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
- package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
- package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
- package/src/components/plan/PlanGrid.vue +1 -1
- package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
- package/src/components/plan-detail/PlanAuditLog.vue +7 -8
- package/src/components/plan-detail/PlanDetail.vue +95 -292
- package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
- package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
- package/src/components/plan-detail/PlanVersionDiffPanel.vue +5 -8
- package/src/components/plan-detail/PlanVersionsPanel.vue +9 -11
- package/src/components/plan-list/PlanList.vue +206 -457
- package/src/components/plan-matrix/PlanMatrix.vue +66 -183
- package/src/components/plan-review/PlanReview.vue +191 -275
- package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
- package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
- package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
- package/src/index.ts +1 -0
- package/src/pages-standard/AdminLayout.vue +38 -12
- package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
- package/src/pages-standard/AuditPage.vue +65 -109
- package/src/pages-standard/BundlesPage.vue +146 -179
- package/src/pages-standard/DashboardPage.vue +59 -154
- package/src/pages-standard/DiscoveryPage.vue +172 -227
- package/src/pages-standard/EmailHistoryPage.vue +118 -159
- package/src/pages-standard/MarketingCatalogPage.vue +399 -440
- package/src/pages-standard/PilotsPage.vue +69 -117
- package/src/pages-standard/PlansPage.vue +240 -112
- package/src/pages-standard/PlatformEmailPage.vue +56 -91
- package/src/pages-standard/PromoCodeDetailPage.vue +49 -74
- package/src/pages-standard/PromoCodesPage.vue +95 -128
- package/src/pages-standard/SubscriptionsPage.vue +19 -52
- package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
- package/src/pages-standard/SuperAdminSetupWizard.vue +22 -18
- package/src/pages-standard/TenantDetailPage.vue +100 -191
- package/src/pages-standard/TenantsPage.vue +190 -295
- package/src/pages-standard/UsersPage.vue +89 -157
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
- package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
- package/src/pages-standard/bundles-page/BundlesToolbar.vue +63 -0
- package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
- package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
- package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +111 -73
- package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +43 -62
- package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +53 -35
- package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +7 -7
- package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
- package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
- package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
- package/src/pages-standard/plans-page/PlansPageToast.vue +6 -6
- package/src/pages-standard/sa-theme.css +552 -86
- package/src/pages-standard/tenant-detail/TenantMasterData.vue +28 -0
- package/src/pages-standard/tenant-detail/TenantUsage.vue +31 -0
- package/src/pages-standard/tenant-detail/TenantUsers.vue +15 -0
- package/src/pages-standard/tenant-detail/types.ts +38 -0
- package/src/pages-standard/tenants/StatusPill.vue +2 -2
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
- package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
- package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
- package/src/pages-tenant/TenantPlanSection.vue +12 -20
- package/src/quasar/create-super-admin-app.ts +4 -0
- package/src/vue/entitlement-provider.ts +0 -1
- package/src/vue/feature-router-guard.ts +0 -1
- package/src/vue/super-admin-context.ts +106 -1
- package/src/vue/use-api-list.ts +2 -1
- package/src/vue/use-mfa-prompt.ts +72 -0
- package/src/vue/use-pagination.ts +63 -0
- package/src/vue/use-plans.ts +1 -2
- package/src/vue/use-sign-out.ts +90 -0
- package/src/vue/use-tenant-billing.ts +1 -1
- package/src/vue/use-tenant-manifest.ts +0 -1
- package/src/components/KpiCard.vue +0 -53
- package/src/components/plan-cockpit/PlanCockpit.vue +0 -1012
- package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
- package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -100
- package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
- package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
- package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
- package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -251
- package/src/components/plan-cockpit/types.ts +0 -72
- package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
- package/src/pages-standard/bundles-page/BundlesHeader.vue +0 -68
- package/src/pages-standard/discovery-page/DiscoveryHeader.vue +0 -34
|
@@ -1,80 +1,70 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
<h1 class="sa-page-head__title">{{ msg.title }}</h1>
|
|
6
|
-
<p v-if="subtitle" class="sa-page-head__sub">{{ subtitle }}</p>
|
|
7
|
-
</div>
|
|
8
|
-
<div class="sa-page-head__actions">
|
|
2
|
+
<AdminPage class="sa-promo-codes">
|
|
3
|
+
<AdminHero :title="msg.title" :subtitle="subtitle">
|
|
4
|
+
<template #actions>
|
|
9
5
|
<slot name="head-actions">
|
|
10
|
-
<
|
|
6
|
+
<button
|
|
11
7
|
v-if="enableCreate"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
icon="add"
|
|
15
|
-
:label="resolvedCreateLabel"
|
|
8
|
+
class="sa-btn sa-btn--primary"
|
|
9
|
+
type="button"
|
|
16
10
|
@click="showCreate = true"
|
|
17
|
-
|
|
11
|
+
>
|
|
12
|
+
<q-icon name="add" size="16px" />
|
|
13
|
+
<span>{{ resolvedCreateLabel }}</span>
|
|
14
|
+
</button>
|
|
18
15
|
</slot>
|
|
19
|
-
</
|
|
20
|
-
</
|
|
21
|
-
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
</
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<template #body-cell-status="{ row }">
|
|
72
|
-
<q-td>
|
|
73
|
-
<q-badge :color="statusColor(row.status)" :label="row.status" />
|
|
74
|
-
</q-td>
|
|
75
|
-
</template>
|
|
76
|
-
<template #body-cell-actions="{ row }">
|
|
77
|
-
<q-td>
|
|
16
|
+
</template>
|
|
17
|
+
</AdminHero>
|
|
18
|
+
|
|
19
|
+
<AdminBody>
|
|
20
|
+
<AdminSection>
|
|
21
|
+
<AdminStatistics :label="msg.title">
|
|
22
|
+
<AdminKpi
|
|
23
|
+
v-for="tile in statTiles"
|
|
24
|
+
:key="tile.id"
|
|
25
|
+
:label="tile.label"
|
|
26
|
+
:value="tile.count"
|
|
27
|
+
:sub="tile.hint"
|
|
28
|
+
:tone="tile.tone"
|
|
29
|
+
:selected="statusFilter === tile.id"
|
|
30
|
+
:action="() => onStatusTileClick(tile.id)"
|
|
31
|
+
/>
|
|
32
|
+
</AdminStatistics>
|
|
33
|
+
</AdminSection>
|
|
34
|
+
|
|
35
|
+
<AdminSection class="sa-promo-codes__card">
|
|
36
|
+
<AdminFilters class="q-mb-lg">
|
|
37
|
+
<q-input
|
|
38
|
+
v-model="filter.search"
|
|
39
|
+
outlined
|
|
40
|
+
dense
|
|
41
|
+
:label="msg.list.searchLabel"
|
|
42
|
+
clearable
|
|
43
|
+
/>
|
|
44
|
+
<q-select
|
|
45
|
+
v-model="filter.status"
|
|
46
|
+
outlined
|
|
47
|
+
dense
|
|
48
|
+
emit-value
|
|
49
|
+
map-options
|
|
50
|
+
clearable
|
|
51
|
+
:label="common.status"
|
|
52
|
+
:options="statusOptions"
|
|
53
|
+
/>
|
|
54
|
+
</AdminFilters>
|
|
55
|
+
|
|
56
|
+
<AdminTable
|
|
57
|
+
:rows="filteredRows"
|
|
58
|
+
:columns="effectiveColumns"
|
|
59
|
+
:loading="loading"
|
|
60
|
+
storage-key="promo-codes"
|
|
61
|
+
>
|
|
62
|
+
<template #body-cell-status="{ row }">
|
|
63
|
+
<q-td>
|
|
64
|
+
<q-badge :color="statusColor(row.status)" :label="row.status" />
|
|
65
|
+
</q-td>
|
|
66
|
+
</template>
|
|
67
|
+
<template #row-actions="{ row }">
|
|
78
68
|
<slot name="row-actions" :row="row" :reload="reload">
|
|
79
69
|
<q-btn
|
|
80
70
|
v-for="action in visibleActions(row)"
|
|
@@ -87,10 +77,10 @@
|
|
|
87
77
|
@click="action.handler(row)"
|
|
88
78
|
/>
|
|
89
79
|
</slot>
|
|
90
|
-
</
|
|
91
|
-
</
|
|
92
|
-
</
|
|
93
|
-
</
|
|
80
|
+
</template>
|
|
81
|
+
</AdminTable>
|
|
82
|
+
</AdminSection>
|
|
83
|
+
</AdminBody>
|
|
94
84
|
|
|
95
85
|
<PromoCodeCreateDialog
|
|
96
86
|
v-if="enableCreate && submitCreate"
|
|
@@ -108,7 +98,7 @@
|
|
|
108
98
|
:submit="submitEdit"
|
|
109
99
|
@updated="onUpdated"
|
|
110
100
|
/>
|
|
111
|
-
</
|
|
101
|
+
</AdminPage>
|
|
112
102
|
</template>
|
|
113
103
|
|
|
114
104
|
<script lang="ts">
|
|
@@ -116,6 +106,7 @@
|
|
|
116
106
|
// `<script setup>` (the whole setup section is wrapped in setup()).
|
|
117
107
|
// Pure helpers + constants therefore live here in the regular `<script>` block.
|
|
118
108
|
|
|
109
|
+
import AdminTable from '../components/admin-page/AdminTable.vue';
|
|
119
110
|
import type { PromoCodePlanOption } from '../components/dialogs/types.js';
|
|
120
111
|
|
|
121
112
|
const PLAN_COLOR_PALETTE = ['#0ea5e9', '#10b981', '#f59e0b', '#7c3aed', '#dc2626', '#64748b'];
|
|
@@ -137,6 +128,13 @@ export function computePlanColors(
|
|
|
137
128
|
|
|
138
129
|
<script setup lang="ts">
|
|
139
130
|
import { computed, onMounted, reactive, ref } from 'vue';
|
|
131
|
+
import AdminBody from '../components/admin-page/AdminBody.vue';
|
|
132
|
+
import AdminFilters from '../components/admin-page/AdminFilters.vue';
|
|
133
|
+
import AdminHero from '../components/admin-page/AdminHero.vue';
|
|
134
|
+
import AdminSection from '../components/admin-page/AdminSection.vue';
|
|
135
|
+
import AdminKpi from '../components/admin-page/AdminKpi.vue';
|
|
136
|
+
import AdminPage from '../components/admin-page/AdminPage.vue';
|
|
137
|
+
import AdminStatistics from '../components/admin-page/AdminStatistics.vue';
|
|
140
138
|
import { useQuasar } from 'quasar';
|
|
141
139
|
import { formatMessage } from '../client/i18n/format.js';
|
|
142
140
|
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
@@ -253,11 +251,25 @@ function classifyRow(row: PromoRow): Exclude<StatusFilter, 'all'> | null {
|
|
|
253
251
|
return null;
|
|
254
252
|
}
|
|
255
253
|
|
|
254
|
+
// The table narrows; the tiles above it deliberately do not — see statTiles.
|
|
256
255
|
const filteredRows = computed(() => {
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
// `clearable` emits null, not '' — see Quasar's use-field clearValue().
|
|
257
|
+
const q = (filter.search ?? '').trim().toLowerCase();
|
|
258
|
+
const status = filter.status?.toUpperCase() ?? null;
|
|
259
|
+
|
|
260
|
+
return rows.value.filter((row) => {
|
|
261
|
+
if (statusFilter.value !== 'all' && classifyRow(row) !== statusFilter.value) return false;
|
|
262
|
+
if (status && String(row.status ?? '').toUpperCase() !== status) return false;
|
|
263
|
+
if (!q) return true;
|
|
264
|
+
return [row.code, row.description, row.campaignTag]
|
|
265
|
+
.filter((v): v is string => typeof v === 'string')
|
|
266
|
+
.some((v) => v.toLowerCase().includes(q));
|
|
267
|
+
});
|
|
259
268
|
});
|
|
260
269
|
|
|
270
|
+
// Absolute counts over everything the tenant has, deliberately independent of
|
|
271
|
+
// the filter below: these tiles are the status filter, so counting the filtered
|
|
272
|
+
// set would drop every unselected tile to zero the moment one is picked.
|
|
261
273
|
const statTiles = computed<
|
|
262
274
|
Array<{
|
|
263
275
|
id: StatusFilter;
|
|
@@ -293,22 +305,8 @@ const statTiles = computed<
|
|
|
293
305
|
];
|
|
294
306
|
});
|
|
295
307
|
|
|
296
|
-
// Tile click: set the filter AND, if a server status match exists, pass it
|
|
297
|
-
// through to the search so `loadPromos` can pre-filter server-side if needed.
|
|
298
308
|
function onStatusTileClick(id: StatusFilter): void {
|
|
299
309
|
statusFilter.value = id;
|
|
300
|
-
const serverStatus =
|
|
301
|
-
id === 'active'
|
|
302
|
-
? 'ACTIVE'
|
|
303
|
-
: id === 'paused'
|
|
304
|
-
? 'PAUSED'
|
|
305
|
-
: id === 'expired'
|
|
306
|
-
? 'EXPIRED'
|
|
307
|
-
: null;
|
|
308
|
-
if (serverStatus !== filter.status) {
|
|
309
|
-
filter.status = serverStatus;
|
|
310
|
-
reload();
|
|
311
|
-
}
|
|
312
310
|
}
|
|
313
311
|
|
|
314
312
|
const baseColumns = computed(() => [
|
|
@@ -396,14 +394,6 @@ const mergedActions = computed<readonly PromoRowAction[]>(() => [
|
|
|
396
394
|
|
|
397
395
|
const effectiveColumns = computed(() => {
|
|
398
396
|
const cols = [...baseColumns.value];
|
|
399
|
-
if (mergedActions.value.length > 0) {
|
|
400
|
-
cols.push({
|
|
401
|
-
name: 'actions',
|
|
402
|
-
label: '',
|
|
403
|
-
field: 'id' as never,
|
|
404
|
-
align: 'right' as 'left',
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
397
|
return cols;
|
|
408
398
|
});
|
|
409
399
|
|
|
@@ -421,10 +411,9 @@ function statusColor(status: string): string {
|
|
|
421
411
|
async function reload() {
|
|
422
412
|
loading.value = true;
|
|
423
413
|
try {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
});
|
|
414
|
+
// Unfiltered on purpose: the tiles count the tenant's full set, and
|
|
415
|
+
// the table narrows locally, so filtering costs no round trip.
|
|
416
|
+
rows.value = await props.loadPromos({});
|
|
428
417
|
} catch (err) {
|
|
429
418
|
rows.value = [];
|
|
430
419
|
console.warn('[PromoCodesPage] loadPromos failed:', err);
|
|
@@ -561,26 +550,4 @@ function formatDate(iso: string | Date | null | undefined): string | null {
|
|
|
561
550
|
}
|
|
562
551
|
</script>
|
|
563
552
|
|
|
564
|
-
<style scoped>
|
|
565
|
-
.sa-promo-codes {
|
|
566
|
-
min-height: calc(100vh - 56px);
|
|
567
|
-
background: var(--sa-bg-app, #f1f5f9);
|
|
568
|
-
padding: 20px 28px 28px;
|
|
569
|
-
}
|
|
570
|
-
.sa-promo-codes__filter {
|
|
571
|
-
display: flex;
|
|
572
|
-
gap: 10px;
|
|
573
|
-
margin-bottom: 12px;
|
|
574
|
-
flex-wrap: wrap;
|
|
575
|
-
}
|
|
576
|
-
.sa-promo-codes__filter > * {
|
|
577
|
-
flex: 1;
|
|
578
|
-
min-width: 200px;
|
|
579
|
-
}
|
|
580
|
-
.sa-promo-codes__card {
|
|
581
|
-
background: #fff;
|
|
582
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
583
|
-
border-radius: 12px;
|
|
584
|
-
overflow: hidden;
|
|
585
|
-
}
|
|
586
|
-
</style>
|
|
553
|
+
<style scoped></style>
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<slot name="head-actions" />
|
|
9
|
-
</header>
|
|
2
|
+
<AdminPage class="sa-subscriptions">
|
|
3
|
+
<AdminHero :title="msg.subscriptions.title" :subtitle="subtitle">
|
|
4
|
+
<template #actions>
|
|
5
|
+
<slot name="head-actions" />
|
|
6
|
+
</template>
|
|
7
|
+
</AdminHero>
|
|
10
8
|
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
flat
|
|
9
|
+
<AdminBody>
|
|
10
|
+
<AdminSection class="sa-subscriptions__body">
|
|
11
|
+
<AdminTable
|
|
15
12
|
:rows="rows"
|
|
16
13
|
:columns="effectiveColumns"
|
|
17
|
-
row-key="id"
|
|
18
|
-
:pagination="{ rowsPerPage: 0 }"
|
|
19
14
|
:loading="loading"
|
|
20
|
-
|
|
15
|
+
storage-key="subscriptions"
|
|
21
16
|
/>
|
|
22
|
-
</
|
|
23
|
-
</
|
|
24
|
-
</
|
|
17
|
+
</AdminSection>
|
|
18
|
+
</AdminBody>
|
|
19
|
+
</AdminPage>
|
|
25
20
|
</template>
|
|
26
21
|
|
|
27
22
|
<script setup lang="ts">
|
|
23
|
+
import AdminTable from '../components/admin-page/AdminTable.vue';
|
|
28
24
|
import { computed, onMounted, ref } from 'vue';
|
|
25
|
+
import AdminBody from '../components/admin-page/AdminBody.vue';
|
|
26
|
+
import AdminHero from '../components/admin-page/AdminHero.vue';
|
|
27
|
+
import AdminSection from '../components/admin-page/AdminSection.vue';
|
|
28
|
+
import AdminPage from '../components/admin-page/AdminPage.vue';
|
|
29
29
|
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
30
30
|
|
|
31
31
|
// Platform standard page: subscriptions (minimal). Apps pass
|
|
@@ -128,37 +128,4 @@ function formatDate(iso: string | null | undefined): string | null {
|
|
|
128
128
|
}
|
|
129
129
|
</script>
|
|
130
130
|
|
|
131
|
-
<style scoped>
|
|
132
|
-
.sa-subscriptions {
|
|
133
|
-
min-height: calc(100vh - 56px);
|
|
134
|
-
background: var(--sa-bg-app, #f1f5f9);
|
|
135
|
-
}
|
|
136
|
-
.sa-subscriptions__head {
|
|
137
|
-
padding: 20px 28px 8px;
|
|
138
|
-
display: flex;
|
|
139
|
-
align-items: flex-start;
|
|
140
|
-
justify-content: space-between;
|
|
141
|
-
gap: 16px;
|
|
142
|
-
}
|
|
143
|
-
.sa-subscriptions__title {
|
|
144
|
-
margin: 0;
|
|
145
|
-
font-family: var(--sa-font-head, system-ui, sans-serif);
|
|
146
|
-
font-weight: 700;
|
|
147
|
-
font-size: 22px;
|
|
148
|
-
color: var(--sa-heading, #0f172a);
|
|
149
|
-
}
|
|
150
|
-
.sa-subscriptions__sub {
|
|
151
|
-
margin: 4px 0 0;
|
|
152
|
-
color: var(--sa-muted-dark, #475569);
|
|
153
|
-
font-size: 13.5px;
|
|
154
|
-
}
|
|
155
|
-
.sa-subscriptions__body {
|
|
156
|
-
padding: 12px 28px 28px;
|
|
157
|
-
}
|
|
158
|
-
.sa-subscriptions__card {
|
|
159
|
-
background: #fff;
|
|
160
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
161
|
-
border-radius: 12px;
|
|
162
|
-
overflow: hidden;
|
|
163
|
-
}
|
|
164
|
-
</style>
|
|
131
|
+
<style scoped></style>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<h1 class="sa-login-title">{{ msg.login.signIn }}</h1>
|
|
25
25
|
<p v-if="subtitle" class="sa-login-subtitle">{{ subtitle }}</p>
|
|
26
26
|
|
|
27
|
-
<q-form
|
|
27
|
+
<q-form class="sa-login-form" @submit.prevent="handleSubmit">
|
|
28
28
|
<q-input
|
|
29
29
|
v-model="form.email"
|
|
30
30
|
:label="msg.emailLabel"
|
|
@@ -111,7 +111,7 @@ interface Props {
|
|
|
111
111
|
subtitle?: string;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
defineProps<Props>();
|
|
115
115
|
|
|
116
116
|
const router = useRouter();
|
|
117
117
|
const msg = useSaMessages('shell');
|
|
@@ -199,7 +199,7 @@ async function handleSubmit(): Promise<void> {
|
|
|
199
199
|
on top of the centered card on short viewports. */
|
|
200
200
|
margin-left: auto;
|
|
201
201
|
align-self: flex-start;
|
|
202
|
-
color: var(--sa-login-tag-color,
|
|
202
|
+
color: var(--sa-login-tag-color, var(--sa-muted-dark));
|
|
203
203
|
}
|
|
204
204
|
.sa-login-wrap {
|
|
205
205
|
min-height: 100vh;
|
|
@@ -208,7 +208,7 @@ async function handleSubmit(): Promise<void> {
|
|
|
208
208
|
align-items: center;
|
|
209
209
|
justify-content: center;
|
|
210
210
|
padding: 24px;
|
|
211
|
-
background: linear-gradient(180deg,
|
|
211
|
+
background: linear-gradient(180deg, var(--sa-heading) 0%, #1e293b 100%);
|
|
212
212
|
}
|
|
213
213
|
.sa-login-card {
|
|
214
214
|
width: 420px;
|
|
@@ -234,7 +234,7 @@ async function handleSubmit(): Promise<void> {
|
|
|
234
234
|
overflow: hidden;
|
|
235
235
|
}
|
|
236
236
|
.sa-login-logo--text {
|
|
237
|
-
background: var(--sa-login-logo-bg, linear-gradient(135deg,
|
|
237
|
+
background: var(--sa-login-logo-bg, linear-gradient(135deg, var(--sa-muted-dark), #1e293b));
|
|
238
238
|
color: var(--sa-login-logo-color, #fff);
|
|
239
239
|
font-weight: 800;
|
|
240
240
|
font-size: 18px;
|
|
@@ -248,12 +248,12 @@ async function handleSubmit(): Promise<void> {
|
|
|
248
248
|
.sa-login-brand__name {
|
|
249
249
|
font-weight: 800;
|
|
250
250
|
font-size: 16px;
|
|
251
|
-
color:
|
|
251
|
+
color: var(--sa-heading);
|
|
252
252
|
line-height: 1.1;
|
|
253
253
|
}
|
|
254
254
|
.sa-login-brand__tag {
|
|
255
255
|
font-size: 12px;
|
|
256
|
-
color: var(--sa-login-tag-color,
|
|
256
|
+
color: var(--sa-login-tag-color, var(--sa-muted-dark));
|
|
257
257
|
text-transform: uppercase;
|
|
258
258
|
letter-spacing: 0.08em;
|
|
259
259
|
}
|
|
@@ -261,12 +261,12 @@ async function handleSubmit(): Promise<void> {
|
|
|
261
261
|
.sa-login-title {
|
|
262
262
|
font-size: 22px;
|
|
263
263
|
font-weight: 800;
|
|
264
|
-
color:
|
|
264
|
+
color: var(--sa-heading);
|
|
265
265
|
margin: 0 0 6px;
|
|
266
266
|
}
|
|
267
267
|
.sa-login-subtitle {
|
|
268
268
|
font-size: 13px;
|
|
269
|
-
color:
|
|
269
|
+
color: var(--sa-muted-dark);
|
|
270
270
|
margin: 0 0 20px;
|
|
271
271
|
line-height: 1.5;
|
|
272
272
|
}
|
|
@@ -290,13 +290,13 @@ async function handleSubmit(): Promise<void> {
|
|
|
290
290
|
.sa-login-hint {
|
|
291
291
|
margin-top: 18px;
|
|
292
292
|
padding: 10px 12px;
|
|
293
|
-
background:
|
|
293
|
+
background: var(--sa-border-soft);
|
|
294
294
|
border-radius: 8px;
|
|
295
295
|
font-size: 12px;
|
|
296
|
-
color:
|
|
296
|
+
color: var(--sa-muted-dark);
|
|
297
297
|
}
|
|
298
298
|
.sa-login-hint code {
|
|
299
|
-
background:
|
|
299
|
+
background: var(--sa-border);
|
|
300
300
|
padding: 1px 5px;
|
|
301
301
|
border-radius: 4px;
|
|
302
302
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<div class="sa-setup-head">
|
|
5
5
|
<div class="sa-setup-badge">{{ iconText }}</div>
|
|
6
6
|
<div>
|
|
7
|
-
<
|
|
7
|
+
<h1 class="sa-setup-title">{{ msg.setup.title }}</h1>
|
|
8
8
|
<div class="sa-setup-sub">{{ subtitleText }}</div>
|
|
9
9
|
</div>
|
|
10
10
|
<LocaleSwitcher class="sa-setup-locale" />
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
</q-banner>
|
|
16
16
|
|
|
17
17
|
<!-- Step 1: create SuperAdmin -->
|
|
18
|
-
<q-form v-if="step === 'form'"
|
|
18
|
+
<q-form v-if="step === 'form'" class="sa-setup-form" @submit.prevent="submitCreate">
|
|
19
19
|
<p class="sa-setup-hint">
|
|
20
20
|
{{ msg.setup.tokenHintBefore }}
|
|
21
21
|
<code>SETUP_TOKEN</code>
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
<code class="sa-setup-uri__value">{{ result.otpauthUri }}</code>
|
|
105
105
|
</details>
|
|
106
106
|
|
|
107
|
-
<q-form
|
|
107
|
+
<q-form class="sa-setup-form q-mt-md" @submit.prevent="submitConfirm">
|
|
108
108
|
<q-input
|
|
109
109
|
v-model="mfaCode"
|
|
110
110
|
:label="msg.setup.codeLabel"
|
|
@@ -216,9 +216,12 @@ async function postJson<T>(path: string, body: unknown): Promise<T> {
|
|
|
216
216
|
return await httpPostJson<T>(http, `${endpoints.apiBase}${path}`, body);
|
|
217
217
|
} catch (err) {
|
|
218
218
|
if (err instanceof HttpJsonError) {
|
|
219
|
+
// The message is translated for the user; `cause` keeps the status
|
|
220
|
+
// and the machine-readable code reachable for anything upstream.
|
|
219
221
|
throw new Error(
|
|
220
222
|
(err.code && errorByCode.value[err.code]) ||
|
|
221
223
|
formatMessage(msg.value.setup.errorHttp, { status: err.status }),
|
|
224
|
+
{ cause: err },
|
|
222
225
|
);
|
|
223
226
|
}
|
|
224
227
|
throw err;
|
|
@@ -274,7 +277,7 @@ async function submitConfirm(): Promise<void> {
|
|
|
274
277
|
align-items: center;
|
|
275
278
|
justify-content: center;
|
|
276
279
|
padding: 24px;
|
|
277
|
-
background: linear-gradient(180deg,
|
|
280
|
+
background: linear-gradient(180deg, var(--sa-heading) 0%, #1e293b 100%);
|
|
278
281
|
}
|
|
279
282
|
.sa-setup-card {
|
|
280
283
|
width: 460px;
|
|
@@ -293,7 +296,7 @@ async function submitConfirm(): Promise<void> {
|
|
|
293
296
|
.sa-setup-locale {
|
|
294
297
|
margin-left: auto;
|
|
295
298
|
align-self: flex-start;
|
|
296
|
-
color:
|
|
299
|
+
color: var(--sa-muted-dark);
|
|
297
300
|
}
|
|
298
301
|
.sa-setup-badge {
|
|
299
302
|
width: 44px;
|
|
@@ -302,30 +305,31 @@ async function submitConfirm(): Promise<void> {
|
|
|
302
305
|
display: flex;
|
|
303
306
|
align-items: center;
|
|
304
307
|
justify-content: center;
|
|
305
|
-
background: linear-gradient(135deg,
|
|
308
|
+
background: linear-gradient(135deg, var(--sa-muted-dark), #1e293b);
|
|
306
309
|
color: #fff;
|
|
307
310
|
font-weight: 800;
|
|
308
311
|
font-size: 18px;
|
|
309
312
|
text-transform: uppercase;
|
|
310
313
|
}
|
|
311
314
|
.sa-setup-title {
|
|
315
|
+
margin: 0;
|
|
312
316
|
font-weight: 800;
|
|
313
317
|
font-size: 18px;
|
|
314
|
-
color:
|
|
318
|
+
color: var(--sa-heading);
|
|
315
319
|
line-height: 1.1;
|
|
316
320
|
}
|
|
317
321
|
.sa-setup-sub {
|
|
318
322
|
font-size: 12px;
|
|
319
|
-
color:
|
|
323
|
+
color: var(--sa-muted-dark);
|
|
320
324
|
}
|
|
321
325
|
.sa-setup-hint {
|
|
322
326
|
font-size: 13px;
|
|
323
|
-
color:
|
|
327
|
+
color: var(--sa-muted-dark);
|
|
324
328
|
line-height: 1.5;
|
|
325
329
|
margin: 0 0 16px;
|
|
326
330
|
}
|
|
327
331
|
.sa-setup-hint code {
|
|
328
|
-
background:
|
|
332
|
+
background: var(--sa-border);
|
|
329
333
|
padding: 1px 5px;
|
|
330
334
|
border-radius: 4px;
|
|
331
335
|
}
|
|
@@ -353,17 +357,17 @@ async function submitConfirm(): Promise<void> {
|
|
|
353
357
|
margin-bottom: 12px;
|
|
354
358
|
}
|
|
355
359
|
.sa-setup-qr img {
|
|
356
|
-
border: 1px solid
|
|
360
|
+
border: 1px solid var(--sa-border);
|
|
357
361
|
border-radius: 8px;
|
|
358
362
|
padding: 8px;
|
|
359
363
|
background: #fff;
|
|
360
364
|
}
|
|
361
365
|
.sa-setup-qr__hint {
|
|
362
366
|
font-size: 12px;
|
|
363
|
-
color:
|
|
367
|
+
color: var(--sa-muted);
|
|
364
368
|
}
|
|
365
369
|
.sa-setup-secret {
|
|
366
|
-
background:
|
|
370
|
+
background: var(--sa-border-soft);
|
|
367
371
|
border-radius: 8px;
|
|
368
372
|
padding: 10px 12px;
|
|
369
373
|
margin-bottom: 10px;
|
|
@@ -372,27 +376,27 @@ async function submitConfirm(): Promise<void> {
|
|
|
372
376
|
font-size: 11px;
|
|
373
377
|
text-transform: uppercase;
|
|
374
378
|
letter-spacing: 0.06em;
|
|
375
|
-
color:
|
|
379
|
+
color: var(--sa-muted);
|
|
376
380
|
margin-bottom: 4px;
|
|
377
381
|
}
|
|
378
382
|
.sa-setup-secret__value {
|
|
379
383
|
font-size: 15px;
|
|
380
384
|
font-weight: 700;
|
|
381
|
-
color:
|
|
385
|
+
color: var(--sa-heading);
|
|
382
386
|
word-break: break-all;
|
|
383
387
|
letter-spacing: 0.04em;
|
|
384
388
|
}
|
|
385
389
|
.sa-setup-uri {
|
|
386
390
|
margin: 4px 0 4px;
|
|
387
391
|
font-size: 12px;
|
|
388
|
-
color:
|
|
392
|
+
color: var(--sa-muted-dark);
|
|
389
393
|
}
|
|
390
394
|
.sa-setup-uri__value {
|
|
391
395
|
display: block;
|
|
392
396
|
margin-top: 6px;
|
|
393
397
|
word-break: break-all;
|
|
394
398
|
color: #334155;
|
|
395
|
-
background:
|
|
399
|
+
background: var(--sa-bg-surface-2);
|
|
396
400
|
padding: 6px 8px;
|
|
397
401
|
border-radius: 6px;
|
|
398
402
|
}
|
|
@@ -401,7 +405,7 @@ async function submitConfirm(): Promise<void> {
|
|
|
401
405
|
width: 100%;
|
|
402
406
|
background: none;
|
|
403
407
|
border: none;
|
|
404
|
-
color:
|
|
408
|
+
color: var(--sa-muted);
|
|
405
409
|
font-size: 12px;
|
|
406
410
|
cursor: pointer;
|
|
407
411
|
text-decoration: underline;
|