@saasicat/ui-vue 0.23.0 → 0.24.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.
Files changed (41) hide show
  1. package/dist/{chunk-XMMFN2WJ.js → chunk-VYMTWCTT.js} +51 -1
  2. package/dist/client/index.cjs +57 -0
  3. package/dist/client/index.d.cts +58 -1
  4. package/dist/client/index.d.ts +58 -1
  5. package/dist/client/index.js +15 -1
  6. package/dist/index.cjs +108 -0
  7. package/dist/index.d.cts +65 -2
  8. package/dist/index.d.ts +65 -2
  9. package/dist/index.js +65 -1
  10. package/package.json +2 -2
  11. package/src/client/identity-accents.ts +129 -0
  12. package/src/client/index.ts +1 -0
  13. package/src/components/MarketingPromotionsTab.vue +6 -2
  14. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +1 -1
  15. package/src/components/dialogs/PromoCodeDialogFields.vue +3 -2
  16. package/src/components/plan-create-dialog/PlanCreateDialog.vue +1 -1
  17. package/src/components/plan-detail/PlanDetail.vue +8 -3
  18. package/src/components/plan-list/PlanList.vue +18 -23
  19. package/src/components/plan-matrix/PlanMatrix.vue +8 -25
  20. package/src/components/plan-review/PlanReview.vue +1 -1
  21. package/src/components/plan-version-editor/PlanVersionEditor.vue +22 -3
  22. package/src/pages-standard/BundlesPage.vue +1 -1
  23. package/src/pages-standard/DashboardPage.vue +5 -2
  24. package/src/pages-standard/MarketingCatalogPage.vue +19 -3
  25. package/src/pages-standard/PromoCodesPage.vue +3 -1
  26. package/src/pages-standard/TenantsPage.vue +5 -4
  27. package/src/pages-standard/discovery-page/discovery-ui.ts +10 -7
  28. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +2 -5
  29. package/src/pages-standard/plan-versions/PlanDiffCard.vue +11 -4
  30. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +3 -1
  31. package/src/pages-standard/tenants/format.ts +11 -12
  32. package/src/pages-tenant/MySubscriptionBundlesPage.vue +9 -0
  33. package/src/pages-tenant/OnboardingConfigurator.vue +1 -1
  34. package/src/pages-tenant/PlanChangeWizard.vue +2 -49
  35. package/src/pages-tenant/TenantPlanSection.vue +6 -48
  36. package/src/pages-tenant/default-i18n.ts +113 -0
  37. package/src/ui/theme/_breakpoints.scss +8 -2
  38. package/src/ui/theme/components/card.css +26 -0
  39. package/src/ui/theme/tokens.primitive.css +4 -0
  40. package/src/ui/theme/tokens.semantic.dark.css +12 -0
  41. package/src/ui/theme/tokens.semantic.light.css +30 -0
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BootLoaderOptions, ManifestLoaderOptions, BuildRouteEntry, SidebarSection, NavBuilderOptions, BatchColumnData, BatchColumnFetcherOptions, BootLoader, ManifestLoader } from './client/index.js';
2
- export { ADMIN_UI_VERSION, AdminPromoListFilter, AdminPromoListRow, AdminResourceClientOptions, BatchColumnDriftError, BatchColumnFetcher, BatchColumnRow, BatchColumnValue, BootLoadError, CachedManifestEntry, DEFAULT_STANDARD_PAGE_ROUTES, HttpJsonError, LoginBootProject, LoginBrandFallback, LoginBranding, ManifestLoadError, MessageParams, ParamStyle, PlanCounts, PlanRowLike, PlanVersionLike, ResolvePlansInput, ResolvedPlan, SidebarItem, buildRoutes, buildSidebar, countPlans, createAdminResourceClient, defaultSectionOrder, formatCurrency, formatMessage, getJson, isCurrentlyValid, isExpired, isFutureScheduled, isProductionBoot, postJson, resolveExtension, resolveLoginBranding, resolvePlans, todayIsoDate, trimTrailingSlashes } from './client/index.js';
2
+ export { ADMIN_UI_VERSION, AdminPromoListFilter, AdminPromoListRow, AdminResourceClientOptions, BatchColumnDriftError, BatchColumnFetcher, BatchColumnRow, BatchColumnValue, BootLoadError, CachedManifestEntry, DEFAULT_STANDARD_PAGE_ROUTES, HttpJsonError, IDENTITY_ACCENTS, IDENTITY_ACCENT_VALUES, IDENTITY_NEUTRAL, IDENTITY_NEUTRAL_VALUE, LoginBootProject, LoginBrandFallback, LoginBranding, ManifestLoadError, MessageParams, ParamStyle, PlanCounts, PlanRowLike, PlanVersionLike, ResolvePlansInput, ResolvedPlan, SidebarItem, buildRoutes, buildSidebar, countPlans, createAdminResourceClient, defaultSectionOrder, formatCurrency, formatMessage, getJson, identityAccentAt, identityAccentFor, identityChipStyle, isCurrentlyValid, isExpired, isFutureScheduled, isProductionBoot, postJson, resolveExtension, resolveLoginBranding, resolvePlans, todayIsoDate, trimTrailingSlashes } from './client/index.js';
3
3
  import { H as HttpClient, e as ActionRegistry, A as ActionHandler, K as KvStore, S as SaLocale } from './catalog-DJcfm5N2.js';
4
4
  export { f as ActionDefNotInManifestError, D as DEFAULT_SA_LOCALE, g as HttpResponse, M as MessageTree, h as MissingHandlerError, P as PartialMessages, R as ResolvedAction, i as SA_INTL_LOCALES, j as SA_LOCALES, k as SA_LOCALE_LABELS, l as SA_MESSAGES, m as SaBuiltinLocale, n as SaCatalog, o as SaCatalogOptions, c as SaLocaleDefinition, b as SaLocaleOption, a as SaMessages, d as SaMessagesOverrides, T as TranslationOf, p as createSaCatalog, q as defaultHttpClient, r as defaultKvStore, s as defineMessages, t as isSaBuiltinLocale, u as mergeMessages, v as resolveMessages } from './catalog-DJcfm5N2.js';
5
5
  import { A as ActionsMap, a as SuperAdminBrand, b as SuperAdminEndpoints, E as ExtensionsMap, c as SuperAdminLoginAdapter } from './use-sa-theme-OUBlaqgl.js';
@@ -1777,5 +1777,68 @@ declare const DEFAULT_I18N_DE: TenantPlanSectionI18n;
1777
1777
  declare const DEFAULT_I18N_EN: TenantPlanSectionI18n;
1778
1778
  /** Default map for the given UI locale — fallback layer under the `i18n` prop. */
1779
1779
  declare function defaultTenantPlanSectionI18n(locale: SaLocale): TenantPlanSectionI18n;
1780
+ /**
1781
+ * The strings `PlanChangeWizard` takes.
1782
+ *
1783
+ * A projection of `TenantPlanSectionI18n` with the `wizard` prefix dropped, so
1784
+ * the wizard's own template reads `i18n.stepChoose` rather than
1785
+ * `i18n.wizardStepChoose`.
1786
+ */
1787
+ interface PlanChangeWizardI18n {
1788
+ title: string;
1789
+ close: string;
1790
+ currentLabel: string;
1791
+ cycleMonthly: string;
1792
+ cycleYearly: string;
1793
+ badgeCurrent: string;
1794
+ badgePopular: string;
1795
+ priceUnitMonthly: string;
1796
+ priceUnitYearly: string;
1797
+ priceOnRequest: string;
1798
+ stepChoose: string;
1799
+ stepChooseIntro: string;
1800
+ stepPreview: string;
1801
+ stepConfirm: string;
1802
+ next: string;
1803
+ back: string;
1804
+ previewLoading: string;
1805
+ effectiveAtLabel: string;
1806
+ effectiveImmediate: string;
1807
+ prorationTitle: string;
1808
+ prorationLine: string;
1809
+ prorationDays: string;
1810
+ limitsTitle: string;
1811
+ limitsUsed: string;
1812
+ limitsCurrent: string;
1813
+ limitsTarget: string;
1814
+ featuresGained: string;
1815
+ featuresLost: string;
1816
+ blockersTitle: string;
1817
+ confirmImmediate: string;
1818
+ confirmScheduled: string;
1819
+ confirmAction: string;
1820
+ confirmInProgress: string;
1821
+ confirmPriceTitle: string;
1822
+ confirmProratedNow: string;
1823
+ confirmRecurringNext: string;
1824
+ confirmRecurringFrom: string;
1825
+ perCycleMonthly: string;
1826
+ perCycleYearly: string;
1827
+ confirmTrialNote: string;
1828
+ confirmRecurringTrialEnd: string;
1829
+ changeTypeUpgrade: string;
1830
+ changeTypeDowngrade: string;
1831
+ changeTypeCycle: string;
1832
+ changeTypeNoop: string;
1833
+ }
1834
+ /**
1835
+ * Builds the wizard's strings from the section's.
1836
+ *
1837
+ * Here rather than inline in `TenantPlanSection.vue`, because the wizard is an
1838
+ * exported component: a consumer who mounts it directly would otherwise have to
1839
+ * reproduce all 44 keys of this mapping by hand, and a visual fixture had to do
1840
+ * exactly that before it moved.
1841
+ */
1842
+ declare function planChangeWizardI18n(i18n: TenantPlanSectionI18n): PlanChangeWizardI18n;
1780
1843
 
1781
- export { ALL_ROWS, ActionHandler, ActionRegistry, ActionsMap, type ApiListResponse, BatchColumnData, BatchColumnFetcherOptions, type BillingCycleStr, BootLoader, BootLoaderOptions, BuildRouteEntry, type BulkItemKind, type BulkItemStatus, type BulkPublishItem, type BundleAddPreviewShape, type BundleCancelPreviewShape, type BundlePreviewIssueShape, type BundlePreviewShape, type BundlePreviewSnapshotShape, BundlesApiError, CATALOG_DEFAULT_LOCALE, type CatalogBundle, CatalogEntriesApiError, type CatalogPlan, type ConfirmDialogState, type CreateAdminRoutesOptions, type CreateManifestStoreOptions, type CreatePlatformLoadersOptions, DEFAULT_I18N_DE, DEFAULT_I18N_EN, DEFAULT_ROWS_PER_PAGE, DEFAULT_YEARLY_FACTOR, DiscoveryLoadError, type DraftPricing, ENTITLEMENT_INJECTION_KEY, type EmailHistoryDetail, type EmailHistoryFilter, type EmailHistoryListResult, type EmailHistoryResendResult, type EmailHistoryRow, type EmailHistoryStatus, type EntitlementSnapshotShape, ExtensionsMap, type FeatureRegistryEntry, type FeatureRouterGuardOptions, type FeatureRowMarkers, HttpClient, KvStore, ManifestLoader, ManifestLoaderOptions, type ManifestStoreActions, type ManifestStoreDefinition, type ManifestStoreState, MarketingProjectionsApiError, type MfaDialogState, NavBuilderOptions, PAGE_SIZE_OPTIONS, type PackageSnapshotShape, type PilotCopy, type PilotCreatePayload, type PilotCreateResult, type PilotEditPayload, type PilotEditResult, type PlanChangePreviewShape, type PlanSnapshotShape, PlannedOnlyFeatureError, PlansApiError, type PlatformEmailProvider, type PlatformEmailTestInput, type PlatformEmailTestResult, type PlatformEmailWriteInput, type PlatformLoaders, type PlatformTenantActionRow, type PlatformTenantActionTone, type PlatformTenantActionsOptions, type PlatformTenantActionsResult, type PriceLineItem, ProjectPageHost, type PromoCodeCreatePayload, type PromoCodeDurationType, type PromoCodePlanOption, type PromoCodeUpdatePayload, type PromoCodeValueType, type PromoState, type PromoStatus, PromotionsApiError, type QuotaMeta, type RedundantFeatureHintShape, type ResolvedTenantAction, SERVER_PAGE_SIZE_OPTIONS, SaLocale, SidebarSection, type SubscriptionBundleShape, type SubscriptionDraft, SuperAdminBrand, SuperAdminEndpoints, SuperAdminLoginAdapter, type TenantActionFlowProviders, type TenantActionInput, type TenantManifestNavItem, type TenantManifestShape, type TenantPlanSectionI18n, type TenantRowLike, TenantSubscriptionBundlesApiError, type UsageSnapshotShape, type UseActionsResult, type UseApiListOptions, type UseApiListResult, type UseAuditEntriesOptions, type UseAuditEntriesResult, type UseBatchColumnsResult, type UseBulkPublishOptions, type UseBulkPublishResult, type UseBundleVersionsMapOptions, type UseBundleVersionsMapResult, type UseBundleVersionsOptions, type UseBundleVersionsResult, type UseBundlesOptions, type UseBundlesResult, type UseCatalogEntriesOptions, type UseCatalogEntriesResult, type UseDiscoveryOptions, type UseDiscoveryResult, type UseEntitlementOptions, type UseEntitlementResult, type UseLivePlanVersionsOptions, type UseLivePlanVersionsResult, type UseManifestResult, type UseMarketingProjectionsOptions, type UseMarketingProjectionsResult, type UseNavResult, type UsePaginationResult, type UsePlanEditorOptions, type UsePlanEditorResult, type UsePlanVersionsOptions, type UsePlanVersionsResult, type UsePlansOptions, type UsePlansResult, type UsePromotionsOptions, type UsePromotionsResult, type UsePublicBootResult, type UseSubscriptionDraftOptions, type UseTenantActionFlowResult, type UseTenantBillingCatalogOptions, type UseTenantBillingCatalogResult, type UseTenantBillingOptions, type UseTenantBillingResult, type UseTenantManifestOptions, type UseTenantManifestResult, type UseTenantSubscriptionBundlesOptions, type UseTenantSubscriptionBundlesResult, type UseTenantsOptions, type UseTenantsResult, buildFeatureRegistry, buildFeatureRouterGuard, buildQuotaRegistry, createAdminRoutes, createManifestStore, createPlatformLoaders, createProjectPageHostRoute, defaultIconForActionKey, defaultTenantPlanSectionI18n, defaultToneForActionKey, provideEntitlement, useActions, useApiList, useAuditEntries, useBatchColumns, useBulkPublish, useBundleVersions, useBundleVersionsMap, useBundles, useCatalogEntries, useDiscovery, useEntitlement, useInjectedEntitlement, useLivePlanVersions, useManifest, useMarketingProjections, useNav, usePagination, usePlanEditor, usePlanVersions, usePlans, usePlatformTenantActions, usePromotions, usePublicBoot, useSubscriptionDraft, useSuperAdminActions, useSuperAdminBrand, useSuperAdminEndpoints, useSuperAdminExtensions, useSuperAdminHttp, useSuperAdminLoginAdapter, useSuperAdminManifest, useTenantActionFlow, useTenantBilling, useTenantBillingCatalog, useTenantManifest, useTenantSubscriptionBundles, useTenants };
1844
+ export { ALL_ROWS, ActionHandler, ActionRegistry, ActionsMap, type ApiListResponse, BatchColumnData, BatchColumnFetcherOptions, type BillingCycleStr, BootLoader, BootLoaderOptions, BuildRouteEntry, type BulkItemKind, type BulkItemStatus, type BulkPublishItem, type BundleAddPreviewShape, type BundleCancelPreviewShape, type BundlePreviewIssueShape, type BundlePreviewShape, type BundlePreviewSnapshotShape, BundlesApiError, CATALOG_DEFAULT_LOCALE, type CatalogBundle, CatalogEntriesApiError, type CatalogPlan, type ConfirmDialogState, type CreateAdminRoutesOptions, type CreateManifestStoreOptions, type CreatePlatformLoadersOptions, DEFAULT_I18N_DE, DEFAULT_I18N_EN, DEFAULT_ROWS_PER_PAGE, DEFAULT_YEARLY_FACTOR, DiscoveryLoadError, type DraftPricing, ENTITLEMENT_INJECTION_KEY, type EmailHistoryDetail, type EmailHistoryFilter, type EmailHistoryListResult, type EmailHistoryResendResult, type EmailHistoryRow, type EmailHistoryStatus, type EntitlementSnapshotShape, ExtensionsMap, type FeatureRegistryEntry, type FeatureRouterGuardOptions, type FeatureRowMarkers, HttpClient, KvStore, ManifestLoader, ManifestLoaderOptions, type ManifestStoreActions, type ManifestStoreDefinition, type ManifestStoreState, MarketingProjectionsApiError, type MfaDialogState, NavBuilderOptions, PAGE_SIZE_OPTIONS, type PackageSnapshotShape, type PilotCopy, type PilotCreatePayload, type PilotCreateResult, type PilotEditPayload, type PilotEditResult, type PlanChangePreviewShape, type PlanChangeWizardI18n, type PlanSnapshotShape, PlannedOnlyFeatureError, PlansApiError, type PlatformEmailProvider, type PlatformEmailTestInput, type PlatformEmailTestResult, type PlatformEmailWriteInput, type PlatformLoaders, type PlatformTenantActionRow, type PlatformTenantActionTone, type PlatformTenantActionsOptions, type PlatformTenantActionsResult, type PriceLineItem, ProjectPageHost, type PromoCodeCreatePayload, type PromoCodeDurationType, type PromoCodePlanOption, type PromoCodeUpdatePayload, type PromoCodeValueType, type PromoState, type PromoStatus, PromotionsApiError, type QuotaMeta, type RedundantFeatureHintShape, type ResolvedTenantAction, SERVER_PAGE_SIZE_OPTIONS, SaLocale, SidebarSection, type SubscriptionBundleShape, type SubscriptionDraft, SuperAdminBrand, SuperAdminEndpoints, SuperAdminLoginAdapter, type TenantActionFlowProviders, type TenantActionInput, type TenantManifestNavItem, type TenantManifestShape, type TenantPlanSectionI18n, type TenantRowLike, TenantSubscriptionBundlesApiError, type UsageSnapshotShape, type UseActionsResult, type UseApiListOptions, type UseApiListResult, type UseAuditEntriesOptions, type UseAuditEntriesResult, type UseBatchColumnsResult, type UseBulkPublishOptions, type UseBulkPublishResult, type UseBundleVersionsMapOptions, type UseBundleVersionsMapResult, type UseBundleVersionsOptions, type UseBundleVersionsResult, type UseBundlesOptions, type UseBundlesResult, type UseCatalogEntriesOptions, type UseCatalogEntriesResult, type UseDiscoveryOptions, type UseDiscoveryResult, type UseEntitlementOptions, type UseEntitlementResult, type UseLivePlanVersionsOptions, type UseLivePlanVersionsResult, type UseManifestResult, type UseMarketingProjectionsOptions, type UseMarketingProjectionsResult, type UseNavResult, type UsePaginationResult, type UsePlanEditorOptions, type UsePlanEditorResult, type UsePlanVersionsOptions, type UsePlanVersionsResult, type UsePlansOptions, type UsePlansResult, type UsePromotionsOptions, type UsePromotionsResult, type UsePublicBootResult, type UseSubscriptionDraftOptions, type UseTenantActionFlowResult, type UseTenantBillingCatalogOptions, type UseTenantBillingCatalogResult, type UseTenantBillingOptions, type UseTenantBillingResult, type UseTenantManifestOptions, type UseTenantManifestResult, type UseTenantSubscriptionBundlesOptions, type UseTenantSubscriptionBundlesResult, type UseTenantsOptions, type UseTenantsResult, buildFeatureRegistry, buildFeatureRouterGuard, buildQuotaRegistry, createAdminRoutes, createManifestStore, createPlatformLoaders, createProjectPageHostRoute, defaultIconForActionKey, defaultTenantPlanSectionI18n, defaultToneForActionKey, planChangeWizardI18n, provideEntitlement, useActions, useApiList, useAuditEntries, useBatchColumns, useBulkPublish, useBundleVersions, useBundleVersionsMap, useBundles, useCatalogEntries, useDiscovery, useEntitlement, useInjectedEntitlement, useLivePlanVersions, useManifest, useMarketingProjections, useNav, usePagination, usePlanEditor, usePlanVersions, usePlans, usePlatformTenantActions, usePromotions, usePublicBoot, useSubscriptionDraft, useSuperAdminActions, useSuperAdminBrand, useSuperAdminEndpoints, useSuperAdminExtensions, useSuperAdminHttp, useSuperAdminLoginAdapter, useSuperAdminManifest, useTenantActionFlow, useTenantBilling, useTenantBillingCatalog, useTenantManifest, useTenantSubscriptionBundles, useTenants };
package/dist/index.js CHANGED
@@ -8,6 +8,10 @@ import {
8
8
  BootLoader,
9
9
  DEFAULT_STANDARD_PAGE_ROUTES,
10
10
  HttpJsonError,
11
+ IDENTITY_ACCENTS,
12
+ IDENTITY_ACCENT_VALUES,
13
+ IDENTITY_NEUTRAL,
14
+ IDENTITY_NEUTRAL_VALUE,
11
15
  ManifestLoadError,
12
16
  ManifestLoader,
13
17
  MissingHandlerError,
@@ -17,6 +21,9 @@ import {
17
21
  createAdminResourceClient,
18
22
  defaultSectionOrder,
19
23
  getJson,
24
+ identityAccentAt,
25
+ identityAccentFor,
26
+ identityChipStyle,
20
27
  isCurrentlyValid,
21
28
  isExpired,
22
29
  isFutureScheduled,
@@ -27,7 +34,7 @@ import {
27
34
  resolvePlans,
28
35
  todayIsoDate,
29
36
  trimTrailingSlashes
30
- } from "./chunk-XMMFN2WJ.js";
37
+ } from "./chunk-VYMTWCTT.js";
31
38
  import {
32
39
  SA_LOCALE_STORAGE_KEY,
33
40
  SA_THEME_KEY,
@@ -2826,6 +2833,55 @@ var DEFAULT_I18N_EN = {
2826
2833
  function defaultTenantPlanSectionI18n(locale) {
2827
2834
  return locale === "en" ? DEFAULT_I18N_EN : DEFAULT_I18N_DE;
2828
2835
  }
2836
+ function planChangeWizardI18n(i18n) {
2837
+ return {
2838
+ title: i18n.wizardTitle,
2839
+ close: i18n.wizardClose,
2840
+ currentLabel: i18n.wizardCurrent,
2841
+ cycleMonthly: i18n.cycleMonthly,
2842
+ cycleYearly: i18n.cycleYearly,
2843
+ badgeCurrent: i18n.wizardBadgeCurrent,
2844
+ badgePopular: i18n.wizardBadgePopular,
2845
+ priceUnitMonthly: i18n.wizardPriceUnitMonthly,
2846
+ priceUnitYearly: i18n.wizardPriceUnitYearly,
2847
+ priceOnRequest: i18n.wizardPriceOnRequest,
2848
+ stepChoose: i18n.wizardStepChoose,
2849
+ stepChooseIntro: i18n.wizardStepChooseIntro,
2850
+ stepPreview: i18n.wizardStepPreview,
2851
+ stepConfirm: i18n.wizardStepConfirm,
2852
+ next: i18n.wizardNext,
2853
+ back: i18n.wizardBack,
2854
+ previewLoading: i18n.wizardPreviewLoading,
2855
+ effectiveAtLabel: i18n.wizardEffectiveAtLabel,
2856
+ effectiveImmediate: i18n.wizardEffectiveImmediate,
2857
+ prorationTitle: i18n.wizardProrationTitle,
2858
+ prorationLine: i18n.wizardProrationLine,
2859
+ prorationDays: i18n.wizardProrationDays,
2860
+ limitsTitle: i18n.wizardLimitsTitle,
2861
+ limitsUsed: i18n.wizardLimitsUsed,
2862
+ limitsCurrent: i18n.wizardLimitsCurrent,
2863
+ limitsTarget: i18n.wizardLimitsTarget,
2864
+ featuresGained: i18n.wizardFeaturesGained,
2865
+ featuresLost: i18n.wizardFeaturesLost,
2866
+ blockersTitle: i18n.wizardBlockersTitle,
2867
+ confirmImmediate: i18n.wizardConfirmImmediate,
2868
+ confirmScheduled: i18n.wizardConfirmScheduled,
2869
+ confirmAction: i18n.wizardConfirmAction,
2870
+ confirmInProgress: i18n.wizardConfirmInProgress,
2871
+ confirmPriceTitle: i18n.wizardConfirmPriceTitle,
2872
+ confirmProratedNow: i18n.wizardConfirmProratedNow,
2873
+ confirmRecurringNext: i18n.wizardConfirmRecurringNext,
2874
+ confirmRecurringFrom: i18n.wizardConfirmRecurringFrom,
2875
+ perCycleMonthly: i18n.wizardConfirmPerCycleMonthly,
2876
+ perCycleYearly: i18n.wizardConfirmPerCycleYearly,
2877
+ confirmTrialNote: i18n.wizardConfirmTrialNote,
2878
+ confirmRecurringTrialEnd: i18n.wizardConfirmRecurringTrialEnd,
2879
+ changeTypeUpgrade: i18n.wizardChangeTypeUpgrade,
2880
+ changeTypeDowngrade: i18n.wizardChangeTypeDowngrade,
2881
+ changeTypeCycle: i18n.wizardChangeTypeCycle,
2882
+ changeTypeNoop: i18n.wizardChangeTypeNoop
2883
+ };
2884
+ }
2829
2885
  export {
2830
2886
  ADMIN_UI_VERSION,
2831
2887
  ALL_ROWS,
@@ -2847,6 +2903,10 @@ export {
2847
2903
  DiscoveryLoadError,
2848
2904
  ENTITLEMENT_INJECTION_KEY,
2849
2905
  HttpJsonError,
2906
+ IDENTITY_ACCENTS,
2907
+ IDENTITY_ACCENT_VALUES,
2908
+ IDENTITY_NEUTRAL,
2909
+ IDENTITY_NEUTRAL_VALUE,
2850
2910
  ManifestLoadError,
2851
2911
  ManifestLoader,
2852
2912
  MarketingProjectionsApiError,
@@ -2899,12 +2959,16 @@ export {
2899
2959
  formatCurrency,
2900
2960
  formatMessage,
2901
2961
  getJson,
2962
+ identityAccentAt,
2963
+ identityAccentFor,
2964
+ identityChipStyle,
2902
2965
  isCurrentlyValid,
2903
2966
  isExpired,
2904
2967
  isFutureScheduled,
2905
2968
  isProductionBoot,
2906
2969
  isSaBuiltinLocale,
2907
2970
  mergeMessages,
2971
+ planChangeWizardI18n,
2908
2972
  postJson,
2909
2973
  provideEntitlement,
2910
2974
  resolveExtension,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasicat/ui-vue",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Vue 3 components, resource clients and composables for the SuperAdmin UI shell. Provides boot and manifest loaders, navigation, actions and standard pages.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -61,7 +61,7 @@
61
61
  "src"
62
62
  ],
63
63
  "dependencies": {
64
- "@saasicat/types": "^0.23.0"
64
+ "@saasicat/types": "^0.24.0"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "pinia": "^2.0.0 || ^3.0.0",
@@ -0,0 +1,129 @@
1
+ // The one categorical palette, and the one way to tint from it.
2
+ //
3
+ // Five components used to carry their own six-colour ramp of hex literals —
4
+ // `PlanList` and `PlanMatrix` byte for byte identical, `tenants/format.ts`
5
+ // silently different (the same plan got one colour on the plans page and
6
+ // another on the tenants page), `PromoCodesPage`, `MarketingPromotionsTab` and
7
+ // `discovery-ui.ts` each with a third opinion. Every one of them was applied
8
+ // through a `:style` binding, so none of them followed the theme: the plan mark
9
+ // measured 2.96:1 on a dark surface and was carried as a named contrast
10
+ // exception because there was no role to point it at.
11
+ //
12
+ // There is now one ramp, it lives in the theme, and it has a value per theme.
13
+ //
14
+ // A consumer may still hand in its own colours — `planAccents` is a public prop
15
+ // and takes any CSS colour. That keeps working because every helper here mixes
16
+ // rather than concatenates: `#7c3aed` + `'15'` needs a hex, and
17
+ // `color-mix(in srgb, X 8%, transparent)` needs only a colour, which a
18
+ // `var(--sa-color-identity-2)` also is.
19
+
20
+ /**
21
+ * The categorical ramp, in the order an unnamed series should walk it.
22
+ *
23
+ * Seven entries, six of them hues plus the neutral. Long enough that a realistic
24
+ * plan list does not wrap, short enough that the colours stay tellable apart —
25
+ * a longer ramp is not a better one, it is a wheel of near-neighbours.
26
+ */
27
+ export const IDENTITY_ACCENTS: readonly string[] = [
28
+ 'var(--sa-color-identity-1)',
29
+ 'var(--sa-color-identity-2)',
30
+ 'var(--sa-color-identity-3)',
31
+ 'var(--sa-color-identity-4)',
32
+ 'var(--sa-color-identity-5)',
33
+ 'var(--sa-color-identity-6)',
34
+ ];
35
+
36
+ /**
37
+ * The same ramp as concrete colours, for values that are STORED rather than
38
+ * painted.
39
+ *
40
+ * A promotion's colour is data: the operator picks it, it goes over the wire,
41
+ * a database column holds it, and `CreatePromotionDto` caps it at 16
42
+ * characters. `var(--sa-color-identity-1)` is 28 and would fail validation on
43
+ * every create — which is exactly what happened when the swatches above were
44
+ * pointed at the token form. A token is paint; this is a value.
45
+ *
46
+ * These are the LIGHT theme's values, because a stored colour has to mean one
47
+ * thing wherever it is later rendered. `identity-accents-match-theme.test.js`
48
+ * binds each one to its role, so the two halves of the ramp cannot drift.
49
+ */
50
+ export const IDENTITY_ACCENT_VALUES: readonly string[] = [
51
+ '#1d4ed8',
52
+ '#6d28d9',
53
+ '#047857',
54
+ '#b45309',
55
+ '#0369a1',
56
+ '#b91c1c',
57
+ ];
58
+
59
+ /** The stored counterpart of `IDENTITY_NEUTRAL`. */
60
+ export const IDENTITY_NEUTRAL_VALUE = '#64748b';
61
+
62
+ /** For "no identity yet", and for the entry tier that should stay quiet. */
63
+ export const IDENTITY_NEUTRAL = 'var(--sa-color-identity-neutral)';
64
+
65
+ /**
66
+ * The accent for the nth member of a series, wrapping when the series is longer
67
+ * than the ramp.
68
+ *
69
+ * A negative or non-finite index yields the neutral rather than throwing:
70
+ * callers derive it from `findIndex`, which answers -1 for "not in the list",
71
+ * and a missing row is exactly the "no identity yet" case.
72
+ */
73
+ export function identityAccentAt(index: number): string {
74
+ if (!Number.isFinite(index) || index < 0) return IDENTITY_NEUTRAL;
75
+ return IDENTITY_ACCENTS[Math.floor(index) % IDENTITY_ACCENTS.length]!;
76
+ }
77
+
78
+ /**
79
+ * The stable accent for a well-known key, so the standard plan tiers look the
80
+ * same on every screen that draws them.
81
+ *
82
+ * The tiers below are the ones the three former maps agreed on. `STARTER` and
83
+ * `BASIC` share the neutral because both are the entry tier — the earlier maps
84
+ * gave them two different greys, which said "these differ" about two things
85
+ * that do not.
86
+ */
87
+ const WELL_KNOWN: Readonly<Record<string, string>> = {
88
+ STARTER: IDENTITY_NEUTRAL,
89
+ BASIC: IDENTITY_NEUTRAL,
90
+ STANDARD: 'var(--sa-color-identity-1)',
91
+ PRO: 'var(--sa-color-identity-2)',
92
+ PROFESSIONAL: 'var(--sa-color-identity-2)',
93
+ BUSINESS: 'var(--sa-color-identity-5)',
94
+ ENTERPRISE: 'var(--sa-color-identity-3)',
95
+ };
96
+
97
+ /**
98
+ * The accent for a key, in the order the product decides it.
99
+ *
100
+ * 1. what the consumer supplied, because it is their brand;
101
+ * 2. the well-known tier, so `ENTERPRISE` is the same colour everywhere;
102
+ * 3. the ramp position, so an unknown key still gets a stable colour;
103
+ * 4. the neutral.
104
+ */
105
+ export function identityAccentFor(
106
+ key: string,
107
+ overrides: Readonly<Record<string, string>> = {},
108
+ index = -1,
109
+ ): string {
110
+ return overrides[key] ?? WELL_KNOWN[key] ?? identityAccentAt(index);
111
+ }
112
+
113
+ /**
114
+ * The three-part chip style every one of these sites was hand-rolling: a wash
115
+ * of the accent, the accent as text, a firmer edge of it.
116
+ *
117
+ * `color-mix()` rather than the `accent + '15'` string concatenation it
118
+ * replaces. That trick only worked on a six-digit hex — it silently produced
119
+ * garbage for `rgb()`, for a named colour and for anything a consumer might
120
+ * reasonably pass — and it could never have worked for a `var()`, which is why
121
+ * these palettes could not follow the theme in the first place.
122
+ */
123
+ export function identityChipStyle(accent: string): Record<string, string> {
124
+ return {
125
+ background: `color-mix(in srgb, ${accent} 8%, transparent)`,
126
+ color: accent,
127
+ borderColor: `color-mix(in srgb, ${accent} 20%, transparent)`,
128
+ };
129
+ }
@@ -26,3 +26,4 @@ export * from './admin-resource-client.js';
26
26
  export * from './i18n/index.js';
27
27
  export * from './login-branding.js';
28
28
  export * from './resolve-plans.js';
29
+ export * from './identity-accents.js';
@@ -361,7 +361,11 @@ const cycleOptions = computed<Array<{ id: PromotionBillingCycle; label: string }
361
361
  { id: 'yearly', label: common.value.yearly },
362
362
  { id: 'both', label: common.value.both },
363
363
  ]);
364
- const COLORS = ['#10b981', '#dc2626', '#f59e0b', '#2563eb', '#7c3aed', '#0f172a'];
364
+ import { IDENTITY_ACCENT_VALUES } from '../client/identity-accents.js';
365
+
366
+ // Concrete values, not tokens: every swatch here is PATCHed to
367
+ // `/catalog/promotions` and stored, and the DTO caps `color` at 16 characters.
368
+ const COLORS = IDENTITY_ACCENT_VALUES;
365
369
 
366
370
  const today = new Date();
367
371
 
@@ -550,7 +554,7 @@ async function onAdd(): Promise<void> {
550
554
  validFrom: new Date().toISOString().slice(0, 10),
551
555
  validTo: new Date(today.getTime() + 30 * 86400000).toISOString().slice(0, 10),
552
556
  priority: 5,
553
- color: '#2563eb',
557
+ color: IDENTITY_ACCENT_VALUES[0],
554
558
  i18n: { [DEFAULT_LOCALE]: { badge: 'Aktion' } },
555
559
  });
556
560
  expandedId.value = created.id;
@@ -506,7 +506,7 @@ function recordsEqual(a: Record<string, number>, b: Record<string, number>): boo
506
506
  grid-template-columns: 1fr 1fr;
507
507
  gap: 18px;
508
508
  }
509
- @media (max-width: 1100px) {
509
+ @media (max-width: 1023.98px) {
510
510
  .bve-grid {
511
511
  grid-template-columns: 1fr;
512
512
  }
@@ -265,6 +265,7 @@
265
265
 
266
266
  <script setup lang="ts">
267
267
  import { computed } from 'vue';
268
+ import { identityChipStyle } from '../../client/identity-accents.js';
268
269
  import { formatMessage } from '../../client/i18n/format.js';
269
270
  import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
270
271
  import type { PromoCodeDurationType, PromoCodePlanOption, PromoCodeValueType } from './types.js';
@@ -376,7 +377,7 @@ function togglePlan(key: string): void {
376
377
 
377
378
  function planChipStyle(p: PromoCodePlanOption): Record<string, string> {
378
379
  if (!isPlanSelected(p.key) || !p.color) return {};
379
- return { borderColor: p.color, background: `${p.color}12`, color: p.color };
380
+ return { ...identityChipStyle(p.color), borderColor: p.color };
380
381
  }
381
382
 
382
383
  const previewValue = computed(() => {
@@ -452,7 +453,7 @@ const advancedOpen = defineModel<boolean>('advancedOpen', { default: false });
452
453
  grid-template-columns: 1fr 1fr;
453
454
  }
454
455
 
455
- @media (max-width: 600px) {
456
+ @media (max-width: 599.98px) {
456
457
  .pc-grid--2 {
457
458
  grid-template-columns: 1fr;
458
459
  }
@@ -497,7 +497,7 @@ function onCancel(): void {
497
497
  display: inline-flex;
498
498
  }
499
499
 
500
- @media (max-width: 540px) {
500
+ @media (max-width: 599.98px) {
501
501
  .pcd-row--2col,
502
502
  .pcd-choice-grid {
503
503
  grid-template-columns: 1fr;
@@ -579,10 +579,15 @@ async function executeTerminate(): Promise<void> {
579
579
  font-weight: 700;
580
580
  }
581
581
  .pd-timeline-seg.draft {
582
+ /* Hatched from the two warning TINTS, not from the saturated amber.
583
+ * `--sa-color-warning-strong` is a border and icon tone: it put the label
584
+ * at 2.34:1 in light and 1.34:1 in dark on every second stripe. The tinted
585
+ * pair is what the guide documents for text on a status surface, and what
586
+ * `.live` above already uses. */
582
587
  background: repeating-linear-gradient(
583
588
  135deg,
584
- var(--sa-color-warning-border) 0 8px,
585
- var(--sa-color-warning-strong) 8px 16px
589
+ var(--sa-color-warning-surface) 0 8px,
590
+ var(--sa-color-warning-surface-strong) 8px 16px
586
591
  );
587
592
  color: var(--sa-color-warning-fg);
588
593
  font-weight: 700;
@@ -1004,7 +1009,7 @@ async function executeTerminate(): Promise<void> {
1004
1009
  flex: 1;
1005
1010
  }
1006
1011
 
1007
- @media (max-width: 1180px) {
1012
+ @media (max-width: 1023.98px) {
1008
1013
  .pd-body {
1009
1014
  grid-template-columns: 1fr;
1010
1015
  }
@@ -45,11 +45,7 @@
45
45
  <div class="sa-plan-list-plan-name">
46
46
  <div
47
47
  class="sa-plan-list-plan-mark"
48
- :style="{
49
- background: planAccent(p.planKey) + '15',
50
- color: planAccent(p.planKey),
51
- borderColor: planAccent(p.planKey) + '33',
52
- }"
48
+ :style="identityChipStyle(planAccent(p.planKey))"
53
49
  >
54
50
  {{ p.planKey.slice(0, 3) }}
55
51
  </div>
@@ -415,6 +411,7 @@
415
411
  import { resolvePlans, type ResolvedPlan } from '../../client/resolve-plans.js';
416
412
  import { computed, ref } from 'vue';
417
413
  import type { PlanRow, PlanVersionRow } from '@saasicat/types';
414
+ import { identityAccentFor, identityChipStyle } from '../../client/identity-accents.js';
418
415
  import { formatMessage } from '../../client/i18n/format.js';
419
416
  import { formatCurrency } from '../../client/i18n/currency.js';
420
417
  import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
@@ -456,24 +453,12 @@ const common = useSaMessages('common');
456
453
  // `clearable` emits null, not '' — see Quasar's use-field clearValue().
457
454
  const search = ref<string | null>('');
458
455
 
459
- const DEFAULT_ACCENTS: Record<string, string> = {
460
- STARTER: '#64748b',
461
- STANDARD: '#2563eb',
462
- PRO: '#7c3aed',
463
- PROFESSIONAL: '#7c3aed',
464
- BUSINESS: '#0ea5e9',
465
- ENTERPRISE: '#0f766e',
466
- BASIC: '#475569',
467
- };
468
- const FALLBACK_ACCENTS = ['#2563eb', '#7c3aed', '#0f766e', '#f59e0b', '#0ea5e9', '#ef4444'];
469
-
470
456
  function planAccent(planKey: string): string {
471
- const provided = props.planAccents[planKey];
472
- if (provided) return provided;
473
- const def = DEFAULT_ACCENTS[planKey];
474
- if (def) return def;
475
- const idx = props.plans.findIndex((p) => p.planKey === planKey);
476
- return FALLBACK_ACCENTS[idx % FALLBACK_ACCENTS.length] ?? FALLBACK_ACCENTS[0]!;
457
+ return identityAccentFor(
458
+ planKey,
459
+ props.planAccents,
460
+ props.plans.findIndex((p) => p.planKey === planKey),
461
+ );
477
462
  }
478
463
 
479
464
  const resolvedPlans = computed(() =>
@@ -566,7 +551,13 @@ function hasAnyPublished(row: ResolvedPlan<PlanRow, PlanVersionRow>): boolean {
566
551
  background: var(--sa-color-bg-surface);
567
552
  border: 1px solid var(--sa-color-border);
568
553
  border-radius: 10px;
569
- overflow: hidden;
554
+ /* Scrolls rather than clips. `overflow: hidden` was here to keep the corner
555
+ * radius, and it also cut the six-column grid off below ~790px — six data
556
+ * columns squeezed into a phone are not readable anyway, so the honest
557
+ * answer is the same as for the tab bar: let it scroll. `hidden` on the
558
+ * cross axis keeps the radius doing its job. */
559
+ overflow-x: auto;
560
+ overflow-y: hidden;
570
561
  }
571
562
  .sa-plan-list-toolbar {
572
563
  display: flex;
@@ -590,6 +581,10 @@ function hasAnyPublished(row: ResolvedPlan<PlanRow, PlanVersionRow>): boolean {
590
581
  .sa-plan-list-list {
591
582
  display: grid;
592
583
  grid-template-columns: 1.6fr 0.9fr 0.7fr 0.9fr 1.4fr 160px;
584
+ /* So the scroll above has something to scroll: without a content floor the
585
+ * tracks shrink to the container and the cells overflow individually,
586
+ * which is the clipped state rather than a scrollable one. */
587
+ min-width: max-content;
593
588
  /* Stretch, not centre. Every row is `display: contents`, so its cells are
594
589
  * grid items in their own right — and a centred item is only as tall as its
595
590
  * own content. The row's hover background is painted per cell, so the band
@@ -272,10 +272,7 @@
272
272
  <span
273
273
  v-if="hasFeature(p, fKey)"
274
274
  class="pm-check"
275
- :style="{
276
- background: planAccent(p.planKey) + '15',
277
- color: planAccent(p.planKey),
278
- }"
275
+ :style="identityChipStyle(planAccent(p.planKey))"
279
276
  >
280
277
  <svg
281
278
  width="12"
@@ -323,10 +320,7 @@
323
320
  <span
324
321
  v-if="hasBundle(p, bKey)"
325
322
  class="pm-check"
326
- :style="{
327
- background: planAccent(p.planKey) + '15',
328
- color: planAccent(p.planKey),
329
- }"
323
+ :style="identityChipStyle(planAccent(p.planKey))"
330
324
  >
331
325
  <svg
332
326
  width="12"
@@ -384,6 +378,7 @@ import { computed } from 'vue';
384
378
  */
385
379
  const NBSP = '\u00A0';
386
380
  import type { PlanRow, PlanVersionRow } from '@saasicat/types';
381
+ import { identityAccentFor, identityChipStyle } from '../../client/identity-accents.js';
387
382
  import { formatMessage } from '../../client/i18n/format.js';
388
383
  import { formatCurrency } from '../../client/i18n/currency.js';
389
384
  import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
@@ -477,24 +472,12 @@ function validFromLabel(validFrom: string): string {
477
472
  return formatMessage(msg.value.matrix.validFrom, { date: validFrom.slice(0, 10) });
478
473
  }
479
474
 
480
- const DEFAULT_ACCENTS: Record<string, string> = {
481
- STARTER: '#64748b',
482
- STANDARD: '#2563eb',
483
- PRO: '#7c3aed',
484
- PROFESSIONAL: '#7c3aed',
485
- BUSINESS: '#0ea5e9',
486
- ENTERPRISE: '#0f766e',
487
- BASIC: '#475569',
488
- };
489
- const FALLBACK_ACCENTS = ['#2563eb', '#7c3aed', '#0f766e', '#f59e0b', '#0ea5e9', '#ef4444'];
490
-
491
475
  function planAccent(planKey: string): string {
492
- const provided = props.planAccents[planKey];
493
- if (provided) return provided;
494
- const def = DEFAULT_ACCENTS[planKey];
495
- if (def) return def;
496
- const idx = props.plans.findIndex((p) => p.planKey === planKey);
497
- return FALLBACK_ACCENTS[idx % FALLBACK_ACCENTS.length] ?? FALLBACK_ACCENTS[0]!;
476
+ return identityAccentFor(
477
+ planKey,
478
+ props.planAccents,
479
+ props.plans.findIndex((p) => p.planKey === planKey),
480
+ );
498
481
  }
499
482
 
500
483
  const resolvedPlans = computed<ResolvedPlan[]>(() =>
@@ -676,7 +676,7 @@ function onPublish(): void {
676
676
  line-height: 1.5;
677
677
  }
678
678
 
679
- @media (max-width: 1100px) {
679
+ @media (max-width: 1023.98px) {
680
680
  .pr-grid {
681
681
  grid-template-columns: 1fr;
682
682
  }
@@ -760,6 +760,10 @@ function emitSave(): void {
760
760
  display: flex;
761
761
  flex-direction: column;
762
762
  box-sizing: border-box;
763
+ /* The editor reflows on ITS OWN width, not the window's — see the
764
+ `@container` rules at the end of this block. */
765
+ container-type: inline-size;
766
+ container-name: pve;
763
767
  /* A full view rather than a modal: the columns stretch to the content area
764
768
  AdminPage already sizes. */
765
769
  height: 100%;
@@ -1546,13 +1550,28 @@ function emitSave(): void {
1546
1550
  display: inline-flex;
1547
1551
  }
1548
1552
 
1549
- /* ── Responsive ────────────────────────────────────────────────── */
1550
- @media (max-width: 1280px) {
1553
+ /* ── Responsive ────────────────────────────────────────────────────
1554
+ *
1555
+ * On the CONTAINER's width, not the viewport's, because the viewport is not
1556
+ * what this grid has to fit into. The admin drawer is 240px and collapsible, so
1557
+ * a viewport threshold is wrong in one of its two states by construction — and
1558
+ * with the drawer open at 1024–1100px the old viewport rule left 784–860px for
1559
+ * `320px 1fr 360px`, squeezing the middle column to 104–180px and clipping its
1560
+ * form controls behind `overflow: hidden`.
1561
+ *
1562
+ * The thresholds are the widths the columns actually need:
1563
+ * 1060px → 320 + 360 fixed leaves ≥ 380px for the basket
1564
+ * 780px → below this the three-column form is not usable at all
1565
+ *
1566
+ * These are container queries, so they are not viewport breakpoints and the
1567
+ * Quasar-band rule does not reach them; `@container` needs no newer browser
1568
+ * than the theme already requires. */
1569
+ @container pve (max-width: 1060px) {
1551
1570
  .pve-body {
1552
1571
  grid-template-columns: 320px 1fr 360px;
1553
1572
  }
1554
1573
  }
1555
- @media (max-width: 1100px) {
1574
+ @container pve (max-width: 780px) {
1556
1575
  .pve-body {
1557
1576
  grid-template-columns: 1fr;
1558
1577
  overflow-y: auto;
@@ -754,7 +754,7 @@ const classifyDiff = computed(() => props.classifyDiff);
754
754
  grid-template-columns: minmax(280px, 360px) 1fr;
755
755
  gap: 18px;
756
756
  }
757
- @media (max-width: 980px) {
757
+ @media (max-width: 1023.98px) {
758
758
  .sa-bd-grid {
759
759
  grid-template-columns: 1fr;
760
760
  }