@saasicat/ui-vue 0.23.0 → 0.24.1

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 (50) hide show
  1. package/dist/{chunk-XMMFN2WJ.js → chunk-D74FFCCP.js} +52 -1
  2. package/dist/client/index.cjs +58 -0
  3. package/dist/client/index.d.cts +70 -1
  4. package/dist/client/index.d.ts +70 -1
  5. package/dist/client/index.js +15 -1
  6. package/dist/index.cjs +109 -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 +167 -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 +38 -16
  16. package/src/components/plan/PromoCodeInput.vue +1 -1
  17. package/src/components/plan-create-dialog/PlanCreateDialog.vue +1 -1
  18. package/src/components/plan-detail/PlanDetail.vue +8 -3
  19. package/src/components/plan-list/PlanList.vue +18 -23
  20. package/src/components/plan-matrix/PlanMatrix.vue +9 -26
  21. package/src/components/plan-review/PlanReview.vue +1 -1
  22. package/src/components/plan-version-editor/PlanVersionEditor.vue +22 -3
  23. package/src/pages-standard/AdminManifestErrorPage.vue +4 -1
  24. package/src/pages-standard/BundlesPage.vue +1 -1
  25. package/src/pages-standard/DashboardPage.vue +8 -5
  26. package/src/pages-standard/DiscoveryPage.vue +42 -8
  27. package/src/pages-standard/MarketingCatalogPage.vue +33 -9
  28. package/src/pages-standard/PromoCodesPage.vue +3 -1
  29. package/src/pages-standard/SuperAdminLoginPage.vue +6 -1
  30. package/src/pages-standard/SuperAdminSetupWizard.vue +1 -1
  31. package/src/pages-standard/TenantsPage.vue +5 -4
  32. package/src/pages-standard/discovery-page/discovery-ui.ts +10 -7
  33. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +2 -5
  34. package/src/pages-standard/plan-versions/PlanDiffCard.vue +11 -4
  35. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +3 -1
  36. package/src/pages-standard/tenants/StatusPill.vue +1 -1
  37. package/src/pages-standard/tenants/format.ts +11 -12
  38. package/src/pages-tenant/MySubscriptionBundlesPage.vue +9 -0
  39. package/src/pages-tenant/OnboardingConfigurator.vue +2 -2
  40. package/src/pages-tenant/PlanChangeWizard.vue +2 -49
  41. package/src/pages-tenant/TenantPlanSection.vue +6 -48
  42. package/src/pages-tenant/default-i18n.ts +113 -0
  43. package/src/ui/theme/_breakpoints.scss +8 -2
  44. package/src/ui/theme/base.css +13 -1
  45. package/src/ui/theme/components/card.css +26 -0
  46. package/src/ui/theme/components/field.css +44 -0
  47. package/src/ui/theme/components/statistics.css +1 -1
  48. package/src/ui/theme/tokens.primitive.css +4 -0
  49. package/src/ui/theme/tokens.semantic.dark.css +12 -0
  50. 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-D74FFCCP.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.1",
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.1"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "pinia": "^2.0.0 || ^3.0.0",
@@ -0,0 +1,167 @@
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
+ * How much of the accent survives when the accent becomes TEXT.
115
+ *
116
+ * The remainder comes from `--sa-color-fg-heading`, which is near-black in the
117
+ * light theme and near-white in the dark one. So a single expression darkens
118
+ * the accent in one theme and lightens it in the other, which is the same move
119
+ * `--sa-color-accent-strong` makes for the brand.
120
+ *
121
+ * It is not the brand's 82 %, and the difference is the point. The brand is one
122
+ * curated colour, checked by a person against both surfaces. This helper's
123
+ * input is uncontrolled: a plan's stored hex, a consumer's `planAccents`, a
124
+ * colour an operator picked out of a swatch. A share tuned for one colour is
125
+ * not a bound over all of them, and 82 % measurably is not one — three of the
126
+ * six colours the promotion editor itself stores stay under 3:1 on a raised
127
+ * dark card (2.81 to 2.98), and a light brand pick such as lime-400 only
128
+ * reaches 2.03:1 on a raised light one.
129
+ *
130
+ * At half, every colour in sRGB stays at or above 3.3:1 in light and 3.7:1 in
131
+ * dark, both extremes being the degenerate accents — an accent that already IS
132
+ * the theme's own extreme cannot be pushed further from the surface, so pure
133
+ * white decides the light bound and pure black the dark one. That is a bound
134
+ * over the whole cube rather than over a sample, which is what an uncontrolled
135
+ * input needs. `theme-role-contrast` measures it and fails if this share rises.
136
+ */
137
+ const READABLE_ACCENT_SHARE = 50;
138
+
139
+ /**
140
+ * The three-part chip style every one of these sites was hand-rolling: a wash
141
+ * of the accent, a readable rendering of the accent as text, a firmer edge.
142
+ *
143
+ * `color-mix()` rather than the `accent + '15'` string concatenation it
144
+ * replaces. That trick only worked on a six-digit hex — it silently produced
145
+ * garbage for `rgb()`, for a named colour and for anything a consumer might
146
+ * reasonably pass — and it could never have worked for a `var()`, which is why
147
+ * these palettes could not follow the theme in the first place.
148
+ *
149
+ * The text is a mix rather than the accent itself. A `var(--sa-color-identity-N)`
150
+ * would not need that — the ramp has a value per theme, so it already suits both
151
+ * surfaces. A concrete hex has one value for both, and a concrete hex is what
152
+ * reaches here from a stored plan colour or from a consumer's `planAccents`; on
153
+ * whichever theme it lands nearest the surface it fades into its own wash.
154
+ *
155
+ * Callers may still override any of the three. `PromoCodeDialogFields` replaces
156
+ * the border with the plan's undiluted colour, and `PlanDiffCard` takes only the
157
+ * wash and the edge — an inline style beats the class beneath it, which is how
158
+ * the chip kept its own unreadable foreground through a fix applied to the
159
+ * selected-state rules around it.
160
+ */
161
+ export function identityChipStyle(accent: string): Record<string, string> {
162
+ return {
163
+ background: `color-mix(in srgb, ${accent} 8%, transparent)`,
164
+ color: `color-mix(in srgb, ${accent} ${READABLE_ACCENT_SHARE}%, var(--sa-color-fg-heading))`,
165
+ borderColor: `color-mix(in srgb, ${accent} 20%, transparent)`,
166
+ };
167
+ }
@@ -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
  }
@@ -101,13 +101,13 @@
101
101
  <div class="pc-grid pc-grid--2">
102
102
  <div class="pc-field">
103
103
  <div class="pc-field__label">{{ msg.form.durationLabel }}</div>
104
- <div class="pc-dur">
104
+ <div class="pc-seg pc-seg--fill">
105
105
  <button
106
106
  v-for="o in durationOptions"
107
107
  :key="o.k"
108
108
  type="button"
109
- class="pc-dur-opt"
110
- :class="{ 'pc-dur-opt--active': form.durationType === o.k }"
109
+ class="pc-seg-opt"
110
+ :class="{ 'pc-seg-opt--active': form.durationType === o.k }"
111
111
  @click="form.durationType = o.k"
112
112
  >
113
113
  {{ o.label }}
@@ -161,13 +161,13 @@
161
161
  <div v-if="mode === 'edit'" class="pc-grid pc-grid--2">
162
162
  <div class="pc-field">
163
163
  <div class="pc-field__label">{{ common.status }}</div>
164
- <div class="pc-status">
164
+ <div class="pc-seg pc-status">
165
165
  <button
166
166
  v-for="o in statusOptions"
167
167
  :key="o.k"
168
168
  type="button"
169
- class="pc-status-opt"
170
- :class="{ 'pc-status-opt--active': form.status === o.k }"
169
+ class="pc-seg-opt"
170
+ :class="{ 'pc-seg-opt--active': form.status === o.k }"
171
171
  @click="form.status = o.k"
172
172
  >
173
173
  <q-icon :name="o.icon" size="14px" />
@@ -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
  }
@@ -594,8 +595,8 @@ textarea.pc-input {
594
595
 
595
596
  .pc-plan-opt--on {
596
597
  border-color: var(--sa-color-accent);
597
- background: var(--sa-color-accent-surface);
598
- color: var(--sa-color-accent);
598
+ background: var(--sa-color-accent-surface-strong);
599
+ color: var(--sa-color-accent-strong);
599
600
  }
600
601
 
601
602
  .pc-plan-opt__mark {
@@ -604,13 +605,25 @@ textarea.pc-input {
604
605
  border-radius: 50%;
605
606
  }
606
607
 
607
- .pc-dur {
608
+ /* One segmented control for both the duration and the status row. They are the
609
+ * same thing — a row of buttons of which one is chosen — and the status row had
610
+ * NO rules at all, so it rendered as raw browser buttons: white boxes on a dark
611
+ * dialog. Sharing the recipe is what makes the two look alike without either
612
+ * restating it. */
613
+ .pc-seg {
608
614
  display: flex;
609
615
  gap: 4px;
610
616
  }
611
-
612
- .pc-dur-opt {
617
+ /* The duration options divide the row; the status options size to their label. */
618
+ .pc-seg--fill .pc-seg-opt {
613
619
  flex: 1;
620
+ }
621
+
622
+ .pc-seg-opt {
623
+ display: inline-flex;
624
+ align-items: center;
625
+ justify-content: center;
626
+ gap: var(--sa-gap-inline);
614
627
  border: 1px solid var(--sa-color-border);
615
628
  background: var(--sa-color-bg-surface);
616
629
  border-radius: 7px;
@@ -620,10 +633,19 @@ textarea.pc-input {
620
633
  color: var(--sa-color-fg-secondary);
621
634
  }
622
635
 
623
- .pc-dur-opt--active {
636
+ .pc-seg-opt:hover {
637
+ border-color: var(--sa-color-border-strong);
638
+ }
639
+
640
+ /* `-accent-strong` on `-surface-strong`, which is what the design guide
641
+ * prescribes for accent text on a tint. The chosen option used to pair plain
642
+ * `--sa-color-accent` with the 8 % wash of itself: in dark mode that is the
643
+ * brand blue on a near-black blue, and the selected state was the one you could
644
+ * read least — the opposite of what selecting something should do. */
645
+ .pc-seg-opt--active {
624
646
  border-color: var(--sa-color-accent);
625
- background: var(--sa-color-accent-surface);
626
- color: var(--sa-color-accent);
647
+ background: var(--sa-color-accent-surface-strong);
648
+ color: var(--sa-color-accent-strong);
627
649
  }
628
650
 
629
651
  .pc-check {
@@ -107,7 +107,7 @@ function remove(): void {
107
107
  border: 1px dashed var(--sa-color-accent-border);
108
108
  background: var(--sa-color-accent-surface-soft);
109
109
  border-radius: 10px;
110
- color: var(--sa-color-accent);
110
+ color: var(--sa-color-accent-strong);
111
111
  font-weight: 700;
112
112
  font-size: var(--sa-text-sm);
113
113
  cursor: pointer;
@@ -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
  }