@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
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { computed, ref, watch, type ComputedRef, type Ref } from 'vue';
|
|
2
|
+
|
|
3
|
+
// Client-side paging for a list that is already fully loaded.
|
|
4
|
+
//
|
|
5
|
+
// Most admin lists fetch everything and filter in the browser; without this
|
|
6
|
+
// they would each carry the same three lines of `slice()` plus the same
|
|
7
|
+
// off-the-end guard. Lists that page on the server (see `useApiList`) bind
|
|
8
|
+
// AdminPaginator to their own state instead and do not need this.
|
|
9
|
+
|
|
10
|
+
/** Quasar's convention: 0 rows per page means no paging at all. */
|
|
11
|
+
export const ALL_ROWS = 0;
|
|
12
|
+
|
|
13
|
+
/** Starting page size for every list; AdminPaginator restores a stored one. */
|
|
14
|
+
export const DEFAULT_ROWS_PER_PAGE = 10;
|
|
15
|
+
|
|
16
|
+
/** The sizes a locally paged list offers, in order. */
|
|
17
|
+
export const PAGE_SIZE_OPTIONS = [10, 25, 50, 100, ALL_ROWS] as const;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The same without "all". A server-paged list cannot honour it: the admin API
|
|
21
|
+
* caps `pageSize` at 200 and falls back to 50 when it is omitted, so the
|
|
22
|
+
* option would claim to show everything while showing the first fifty.
|
|
23
|
+
*/
|
|
24
|
+
export const SERVER_PAGE_SIZE_OPTIONS = [10, 25, 50, 100] as const;
|
|
25
|
+
|
|
26
|
+
export interface UsePaginationResult<T> {
|
|
27
|
+
page: Ref<number>;
|
|
28
|
+
rowsPerPage: Ref<number>;
|
|
29
|
+
/** Total across all pages — what AdminPaginator counts with. */
|
|
30
|
+
total: ComputedRef<number>;
|
|
31
|
+
/** The slice to render. */
|
|
32
|
+
pagedRows: ComputedRef<T[]>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function usePagination<T>(
|
|
36
|
+
rows: Ref<readonly T[]> | ComputedRef<readonly T[]>,
|
|
37
|
+
initialRowsPerPage = DEFAULT_ROWS_PER_PAGE,
|
|
38
|
+
): UsePaginationResult<T> {
|
|
39
|
+
const page = ref(1);
|
|
40
|
+
const rowsPerPage = ref(initialRowsPerPage);
|
|
41
|
+
|
|
42
|
+
const total = computed(() => rows.value.length);
|
|
43
|
+
|
|
44
|
+
const pageCount = computed(() =>
|
|
45
|
+
rowsPerPage.value === ALL_ROWS
|
|
46
|
+
? 1
|
|
47
|
+
: Math.max(1, Math.ceil(total.value / rowsPerPage.value)),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Filtering can shrink a list under the current page — without this the
|
|
51
|
+
// table would render an empty slice and look like it lost its rows.
|
|
52
|
+
watch(pageCount, (count) => {
|
|
53
|
+
if (page.value > count) page.value = count;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const pagedRows = computed(() => {
|
|
57
|
+
if (rowsPerPage.value === ALL_ROWS) return [...rows.value];
|
|
58
|
+
const start = (page.value - 1) * rowsPerPage.value;
|
|
59
|
+
return rows.value.slice(start, start + rowsPerPage.value);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return { page, rowsPerPage, total, pagedRows };
|
|
63
|
+
}
|
package/src/vue/use-plans.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// usePlans + usePlanVersions — Vue 3 composables for the SuperAdmin
|
|
2
|
-
// plan management
|
|
2
|
+
// plan management.:
|
|
3
3
|
// Pack 1 → usePlans (root CRUD)
|
|
4
4
|
// Pack 2a → usePlanVersions (draft/publish lifecycle)
|
|
5
5
|
|
|
@@ -192,7 +192,6 @@ export function usePlans(options: UsePlansOptions): UsePlansResult {
|
|
|
192
192
|
|
|
193
193
|
// =============================================================================
|
|
194
194
|
// usePlanVersions — lifecycle operations for a specific plan ID
|
|
195
|
-
// (SPEC_V2 §11.1 M6 Pack 2a).
|
|
196
195
|
// =============================================================================
|
|
197
196
|
|
|
198
197
|
export interface UsePlanVersionsOptions {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Signing out, in one place.
|
|
2
|
+
//
|
|
3
|
+
// Both platform affordances that offer it — the layout header button and the
|
|
4
|
+
// fail-closed error page — are mounted as bare route components in every
|
|
5
|
+
// consumer we know of, which means neither an `onLogout` prop nor a `@logout`
|
|
6
|
+
// listener reaches them. Their fallback therefore has to be the real thing.
|
|
7
|
+
//
|
|
8
|
+
// Navigating to `/login` on its own is not it. An app's `isAuthenticated()`
|
|
9
|
+
// typically reads a token out of storage, so a session left behind means the
|
|
10
|
+
// next navigation to `/admin` passes the guard again: the operator clicked
|
|
11
|
+
// "sign out", saw the login form, and is still signed in.
|
|
12
|
+
//
|
|
13
|
+
// The adapter that knows how to start a session is the only thing that knows
|
|
14
|
+
// how to end it, hence `loginAdapter.logout`.
|
|
15
|
+
|
|
16
|
+
import { inject } from 'vue';
|
|
17
|
+
import { useRouter } from 'vue-router';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
SUPER_ADMIN_LOGIN_ADAPTER_KEY,
|
|
21
|
+
SUPER_ADMIN_MANIFEST_CLEAR_CACHE_KEY,
|
|
22
|
+
} from './super-admin-context.js';
|
|
23
|
+
|
|
24
|
+
/** Where `createAdminRoutes()` registers the login page. */
|
|
25
|
+
export const DEFAULT_LOGIN_PATH = '/login';
|
|
26
|
+
|
|
27
|
+
export interface UseSignOutOptions {
|
|
28
|
+
/** Route to land on afterwards. Default `/login`. */
|
|
29
|
+
loginPath?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns the platform's default sign-out: end the session, then go to login.
|
|
34
|
+
*
|
|
35
|
+
* Must be called during component setup — it injects the login adapter and
|
|
36
|
+
* uses the router.
|
|
37
|
+
*/
|
|
38
|
+
export function useSignOut(options: UseSignOutOptions = {}): () => Promise<void> {
|
|
39
|
+
const router = useRouter();
|
|
40
|
+
const adapter = inject(SUPER_ADMIN_LOGIN_ADAPTER_KEY, undefined);
|
|
41
|
+
const clearManifestCache = inject(SUPER_ADMIN_MANIFEST_CLEAR_CACHE_KEY, undefined);
|
|
42
|
+
|
|
43
|
+
return async function signOut(): Promise<void> {
|
|
44
|
+
if (adapter?.logout) {
|
|
45
|
+
try {
|
|
46
|
+
await adapter.logout();
|
|
47
|
+
} catch (err) {
|
|
48
|
+
// Navigate anyway. A rejected `logout()` — a server-side
|
|
49
|
+
// revocation that failed, say — must not strand the operator on
|
|
50
|
+
// the protected page they just asked to leave, and both callers
|
|
51
|
+
// discard this promise, so an escaping rejection would be
|
|
52
|
+
// invisible. Leaving is also the safer half: the adapter may
|
|
53
|
+
// well have cleared the local token before the request failed.
|
|
54
|
+
console.error(
|
|
55
|
+
'[SaaSiCat] Sign-out failed while ending the session — navigating to the login page anyway. ' +
|
|
56
|
+
'The session may still be valid on the server.',
|
|
57
|
+
err,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// Loud rather than silent: the button looks like it worked either
|
|
62
|
+
// way, so the only place this can surface is the console.
|
|
63
|
+
console.warn(
|
|
64
|
+
'[SaaSiCat] Sign-out only navigated to the login page — the session was left ' +
|
|
65
|
+
'intact, so the next navigation to /admin will pass the guard again. Add ' +
|
|
66
|
+
'`logout()` to the `loginAdapter` you pass to createSuperAdminApp().',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Unconditionally, and after the adapter either way: the manifest store
|
|
71
|
+
// stays `loaded` across a sign-out, so without this the next
|
|
72
|
+
// `ensureLoaded()` returns instantly and hands the *next* operator the
|
|
73
|
+
// previous session's manifest — their navigation, their capabilities,
|
|
74
|
+
// their project. Only a full page reload would have cleared it, and
|
|
75
|
+
// signing out does not force one.
|
|
76
|
+
if (clearManifestCache) {
|
|
77
|
+
try {
|
|
78
|
+
await clearManifestCache();
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(
|
|
81
|
+
'[SaaSiCat] Sign-out could not discard the cached manifest. The next login in ' +
|
|
82
|
+
'this tab may see the previous session’s navigation until the page reloads.',
|
|
83
|
+
err,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await router.replace(options.loginPath ?? DEFAULT_LOGIN_PATH);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -56,7 +56,7 @@ export interface UsageSnapshotShape {
|
|
|
56
56
|
};
|
|
57
57
|
usage: Record<string, number>;
|
|
58
58
|
/**
|
|
59
|
-
* P11.4
|
|
59
|
+
* P11.4: Read-only package snapshot from the
|
|
60
60
|
* original CheckoutOffer. `null` for subscriptions without a
|
|
61
61
|
* CheckoutOffer origin. The JSON structure matches the Offer
|
|
62
62
|
* schema; the UI can read `bundleVersionIds`, `currency`,
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
// backend renders per Tenant. This lets the app UI build the navigation
|
|
6
6
|
// declaratively from the Manifest instead of filtering it itself.
|
|
7
7
|
//
|
|
8
|
-
// Spec: handoff/superadmin/QUICKSTART_SIMPLIFICATIONS.md §P14.
|
|
9
8
|
|
|
10
9
|
import { ref, type Ref } from 'vue';
|
|
11
10
|
import { defaultHttpClient, type HttpClient } from '../client/types.js';
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="kpi" :style="{ borderTopColor: accent }">
|
|
3
|
-
<div class="kpi__head">
|
|
4
|
-
<q-icon :name="icon" size="20px" :style="{ color: accent }" />
|
|
5
|
-
<span class="kpi__label">{{ label }}</span>
|
|
6
|
-
</div>
|
|
7
|
-
<div class="kpi__value">{{ value }}</div>
|
|
8
|
-
<slot name="footer" />
|
|
9
|
-
</div>
|
|
10
|
-
</template>
|
|
11
|
-
|
|
12
|
-
<script setup lang="ts">
|
|
13
|
-
// Generic KPI card with icon, label, value and optional footer slot.
|
|
14
|
-
// Accent color via `accent` prop (CSS color).
|
|
15
|
-
|
|
16
|
-
defineProps<{
|
|
17
|
-
label: string;
|
|
18
|
-
value: string | number;
|
|
19
|
-
icon: string;
|
|
20
|
-
accent?: string;
|
|
21
|
-
}>();
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<style scoped>
|
|
25
|
-
.kpi {
|
|
26
|
-
background: #fff;
|
|
27
|
-
border: 1px solid #e2e8f0;
|
|
28
|
-
border-top-width: 3px;
|
|
29
|
-
border-radius: 14px;
|
|
30
|
-
padding: 16px 18px;
|
|
31
|
-
display: flex;
|
|
32
|
-
flex-direction: column;
|
|
33
|
-
gap: 6px;
|
|
34
|
-
}
|
|
35
|
-
.kpi__head {
|
|
36
|
-
display: flex;
|
|
37
|
-
align-items: center;
|
|
38
|
-
gap: 8px;
|
|
39
|
-
}
|
|
40
|
-
.kpi__label {
|
|
41
|
-
font-size: 11px;
|
|
42
|
-
color: #64748b;
|
|
43
|
-
text-transform: uppercase;
|
|
44
|
-
letter-spacing: 0.05em;
|
|
45
|
-
font-weight: 700;
|
|
46
|
-
}
|
|
47
|
-
.kpi__value {
|
|
48
|
-
font-size: 24px;
|
|
49
|
-
font-weight: 800;
|
|
50
|
-
color: #0f172a;
|
|
51
|
-
line-height: 1.1;
|
|
52
|
-
}
|
|
53
|
-
</style>
|