@saasicat/ui-vue 0.2.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 (193) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +47 -0
  3. package/dist/index.cjs +3776 -0
  4. package/dist/index.d.cts +2395 -0
  5. package/dist/index.d.ts +2395 -0
  6. package/dist/index.js +3659 -0
  7. package/dist/testing-e2e/admin-pages-suite.cjs +134 -0
  8. package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
  9. package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
  10. package/dist/testing-e2e/admin-pages-suite.js +109 -0
  11. package/package.json +87 -0
  12. package/src/action-registry.ts +110 -0
  13. package/src/batch-column-fetcher.ts +152 -0
  14. package/src/boot-loader.ts +60 -0
  15. package/src/components/BundleVersionPublishDialog.vue +359 -0
  16. package/src/components/BusinessTypeVersionEditorDialog.vue +417 -0
  17. package/src/components/BusinessTypeVersionPublishDialog.vue +258 -0
  18. package/src/components/FeatureGate.vue +56 -0
  19. package/src/components/KpiCard.vue +53 -0
  20. package/src/components/KvBlock.vue +36 -0
  21. package/src/components/MarketingPromotionsTab.vue +861 -0
  22. package/src/components/MfaPromptDialog.vue +104 -0
  23. package/src/components/TenantActionConfirmDialog.vue +252 -0
  24. package/src/components/VersionDiffPreview.vue +150 -0
  25. package/src/components/bundle-editor/BundleCreatePanel.vue +681 -0
  26. package/src/components/bundle-editor/BundleFeaturesEditor.vue +215 -0
  27. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +316 -0
  28. package/src/components/bundle-editor/BundleQuotasEditor.vue +219 -0
  29. package/src/components/bundle-editor/BundleStatusBanner.vue +189 -0
  30. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +659 -0
  31. package/src/components/bundle-editor/BundleVersionStrip.vue +207 -0
  32. package/src/components/bundle-editor/bundle-version-status.ts +164 -0
  33. package/src/components/bundle-editor/catalog-i18n.ts +85 -0
  34. package/src/components/dialogs/PilotCreateDialog.vue +780 -0
  35. package/src/components/dialogs/PilotEditDialog.vue +548 -0
  36. package/src/components/dialogs/PromoCodeCreateDialog.vue +737 -0
  37. package/src/components/dialogs/PromoCodeEditDialog.vue +859 -0
  38. package/src/components/dialogs/types.ts +126 -0
  39. package/src/components/plan/PlanCycleToggle.vue +99 -0
  40. package/src/components/plan/PlanGrid.vue +211 -0
  41. package/src/components/plan/PriceSummary.vue +293 -0
  42. package/src/components/plan/PromoCodeInput.vue +192 -0
  43. package/src/components/plan/PublicBundleGrid.vue +287 -0
  44. package/src/components/plan-cockpit/PlanCockpit.vue +993 -0
  45. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +65 -0
  46. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +91 -0
  47. package/src/components/plan-cockpit/PlanCockpitHeader.vue +98 -0
  48. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +99 -0
  49. package/src/components/plan-cockpit/PlanCockpitKpis.vue +57 -0
  50. package/src/components/plan-cockpit/PlanCockpitVersions.vue +226 -0
  51. package/src/components/plan-cockpit/types.ts +72 -0
  52. package/src/components/plan-create-dialog/PlanCreateDialog.vue +508 -0
  53. package/src/components/plan-detail/PlanAuditLog.vue +59 -0
  54. package/src/components/plan-detail/PlanDetail.vue +1195 -0
  55. package/src/components/plan-detail/PlanDetailHeader.vue +185 -0
  56. package/src/components/plan-detail/PlanDetailKpis.vue +57 -0
  57. package/src/components/plan-detail/PlanTerminateDialog.vue +76 -0
  58. package/src/components/plan-detail/PlanVersionDiffPanel.vue +149 -0
  59. package/src/components/plan-detail/PlanVersionsPanel.vue +255 -0
  60. package/src/components/plan-detail/types.ts +59 -0
  61. package/src/components/plan-list/PlanList.vue +1067 -0
  62. package/src/components/plan-matrix/PlanMatrix.vue +1113 -0
  63. package/src/components/plan-review/PlanReview.vue +742 -0
  64. package/src/components/plan-version-editor/PlanCatalogPreview.vue +184 -0
  65. package/src/components/plan-version-editor/PlanComponentPool.vue +272 -0
  66. package/src/components/plan-version-editor/PlanVersionBasket.vue +281 -0
  67. package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +84 -0
  68. package/src/components/plan-version-editor/PlanVersionEditor.vue +1728 -0
  69. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +94 -0
  70. package/src/components/plan-version-editor/types.ts +96 -0
  71. package/src/components/wizard-stepper/WizardStepper.vue +107 -0
  72. package/src/create-admin-routes.ts +68 -0
  73. package/src/create-super-admin-app.ts +345 -0
  74. package/src/entitlement-provider.ts +36 -0
  75. package/src/feature-router-guard.ts +77 -0
  76. package/src/http-json.ts +62 -0
  77. package/src/index.ts +98 -0
  78. package/src/manifest-loader.ts +139 -0
  79. package/src/manifest-store-factory.ts +114 -0
  80. package/src/nav-builder.ts +274 -0
  81. package/src/pages-standard/AdminLayout.vue +411 -0
  82. package/src/pages-standard/AdminManifestErrorPage.vue +79 -0
  83. package/src/pages-standard/AuditPage.vue +223 -0
  84. package/src/pages-standard/BundlesPage.vue +910 -0
  85. package/src/pages-standard/BusinessTypesPage.vue +662 -0
  86. package/src/pages-standard/DashboardPage.vue +595 -0
  87. package/src/pages-standard/DiscoveryPage.vue +663 -0
  88. package/src/pages-standard/EmailHistoryPage.vue +576 -0
  89. package/src/pages-standard/MarketingCatalogPage.vue +1771 -0
  90. package/src/pages-standard/PilotsPage.vue +577 -0
  91. package/src/pages-standard/PlanVersionsPage.vue +216 -0
  92. package/src/pages-standard/PlansPage.vue +1222 -0
  93. package/src/pages-standard/PlatformEmailPage.vue +434 -0
  94. package/src/pages-standard/PromoCodeDetailPage.vue +331 -0
  95. package/src/pages-standard/PromoCodesPage.vue +589 -0
  96. package/src/pages-standard/SubscriptionsPage.vue +159 -0
  97. package/src/pages-standard/SuperAdminLoginPage.vue +299 -0
  98. package/src/pages-standard/SuperAdminSetupWizard.vue +405 -0
  99. package/src/pages-standard/TenantDetailPage.vue +432 -0
  100. package/src/pages-standard/TenantsPage.vue +732 -0
  101. package/src/pages-standard/UsersPage.vue +504 -0
  102. package/src/pages-standard/bundles-page/BundleAccordionList.vue +116 -0
  103. package/src/pages-standard/bundles-page/BundleDetailPanel.vue +193 -0
  104. package/src/pages-standard/bundles-page/BundlesFilterBar.vue +48 -0
  105. package/src/pages-standard/bundles-page/BundlesHeader.vue +67 -0
  106. package/src/pages-standard/bundles-page/BundlesKpis.vue +39 -0
  107. package/src/pages-standard/bundles-page/types.ts +15 -0
  108. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +246 -0
  109. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +171 -0
  110. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +410 -0
  111. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +31 -0
  112. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +47 -0
  113. package/src/pages-standard/discovery-page/DiscoveryMetaBanner.vue +26 -0
  114. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +247 -0
  115. package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +73 -0
  116. package/src/pages-standard/discovery-page/discovery-ui.ts +166 -0
  117. package/src/pages-standard/email-history.types.ts +48 -0
  118. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +448 -0
  119. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +93 -0
  120. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +138 -0
  121. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +58 -0
  122. package/src/pages-standard/marketing-catalog/types.ts +43 -0
  123. package/src/pages-standard/plan-versions/PlanDiffCard.vue +132 -0
  124. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +293 -0
  125. package/src/pages-standard/plan-versions/PlanVersionStatusBadge.vue +48 -0
  126. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +307 -0
  127. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +269 -0
  128. package/src/pages-standard/plan-versions/PlanVersionsKpi.vue +65 -0
  129. package/src/pages-standard/plan-versions/PlanVersionsList.vue +292 -0
  130. package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +261 -0
  131. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +300 -0
  132. package/src/pages-standard/plan-versions/VersionDiffPreview.vue +167 -0
  133. package/src/pages-standard/plan-versions/format.ts +53 -0
  134. package/src/pages-standard/plan-versions/types.ts +33 -0
  135. package/src/pages-standard/plans-page/PlanArchiveDialog.vue +50 -0
  136. package/src/pages-standard/plans-page/PlanBundleOverview.vue +147 -0
  137. package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +46 -0
  138. package/src/pages-standard/plans-page/PlanPublishDialog.vue +106 -0
  139. package/src/pages-standard/plans-page/PlansPageToast.vue +69 -0
  140. package/src/pages-standard/plans-page/types.ts +18 -0
  141. package/src/pages-standard/platform-email.types.ts +41 -0
  142. package/src/pages-standard/sa-theme.css +223 -0
  143. package/src/pages-standard/tenants/StatusPill.vue +62 -0
  144. package/src/pages-standard/tenants/format.ts +38 -0
  145. package/src/pages-tenant/LimitsRow.vue +67 -0
  146. package/src/pages-tenant/MySubscriptionBundlesPage.vue +604 -0
  147. package/src/pages-tenant/OnboardingConfigurator.vue +417 -0
  148. package/src/pages-tenant/PackageSnapshotPanel.vue +241 -0
  149. package/src/pages-tenant/PendingVersionBanner.vue +126 -0
  150. package/src/pages-tenant/PlanChangeWizard.vue +630 -0
  151. package/src/pages-tenant/TenantPlanSection.vue +729 -0
  152. package/src/pages-tenant/UsageBar.vue +107 -0
  153. package/src/pages-tenant/default-i18n.ts +280 -0
  154. package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +304 -0
  155. package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +323 -0
  156. package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +122 -0
  157. package/src/pages-tenant/tenant-plan-section/TenantPlanCardHeader.vue +67 -0
  158. package/src/pages-tenant/tenant-plan-section/TenantUsageGrid.vue +31 -0
  159. package/src/plan-versions-catalog.ts +368 -0
  160. package/src/platform-loaders.ts +68 -0
  161. package/src/project-page-host.ts +108 -0
  162. package/src/testing-e2e/admin-pages-suite.ts +187 -0
  163. package/src/types.ts +58 -0
  164. package/src/use-actions.ts +25 -0
  165. package/src/use-api-list.ts +151 -0
  166. package/src/use-audit-entries.ts +48 -0
  167. package/src/use-batch-columns.ts +59 -0
  168. package/src/use-bulk-publish.ts +150 -0
  169. package/src/use-bundle-versions-map.ts +112 -0
  170. package/src/use-bundles.ts +297 -0
  171. package/src/use-business-types.ts +269 -0
  172. package/src/use-catalog-entries.ts +243 -0
  173. package/src/use-discovery.ts +158 -0
  174. package/src/use-entitlement.ts +87 -0
  175. package/src/use-live-plan-versions.ts +126 -0
  176. package/src/use-manifest.ts +57 -0
  177. package/src/use-marketing-projections.ts +160 -0
  178. package/src/use-nav.ts +33 -0
  179. package/src/use-plan-editor.ts +162 -0
  180. package/src/use-plan-versions.ts +83 -0
  181. package/src/use-plans.ts +375 -0
  182. package/src/use-platform-tenant-actions.ts +286 -0
  183. package/src/use-promotions.ts +126 -0
  184. package/src/use-public-boot.ts +40 -0
  185. package/src/use-subscription-draft.ts +355 -0
  186. package/src/use-super-admin-context.ts +93 -0
  187. package/src/use-tenant-action-flow.ts +238 -0
  188. package/src/use-tenant-billing-catalog.ts +160 -0
  189. package/src/use-tenant-billing.ts +453 -0
  190. package/src/use-tenant-manifest.ts +94 -0
  191. package/src/use-tenant-subscription-bundles.ts +148 -0
  192. package/src/use-tenants.ts +61 -0
  193. package/src/version.ts +10 -0
@@ -0,0 +1,147 @@
1
+ <template>
2
+ <section class="sa-bundles">
3
+ <div class="sa-bundles__head">
4
+ <h2 class="sa-bundles__title">Bundles</h2>
5
+ <span class="sa-bundles__count">{{ bundles.length }}</span>
6
+ </div>
7
+
8
+ <div v-if="bundles.length === 0" class="sa-bundles__empty">
9
+ Keine Bundles vorhanden.
10
+ </div>
11
+
12
+ <div v-else class="sa-bundles__grid">
13
+ <q-card v-for="bundle in bundles" :key="bundle.bundleKey" flat bordered class="sa-bundle">
14
+ <q-card-section class="sa-bundle__header">
15
+ <div class="sa-bundle__label">{{ bundle.label ?? bundle.bundleKey }}</div>
16
+ <div class="sa-bundle__key">{{ bundle.bundleKey }}</div>
17
+ </q-card-section>
18
+ <q-separator />
19
+ <q-card-section>
20
+ <div class="sa-bundle__caption">Enthält</div>
21
+ <div class="sa-bundle__chips">
22
+ <q-chip
23
+ v-for="feature in bundle.features"
24
+ :key="feature"
25
+ dense
26
+ square
27
+ size="sm"
28
+ class="sa-bundle__chip"
29
+ >
30
+ {{ featureLabel(feature) }}
31
+ </q-chip>
32
+ <span v-if="bundle.features.length === 0" class="sa-bundle__muted">—</span>
33
+ </div>
34
+ </q-card-section>
35
+ <q-card-section class="sa-bundle__compat">
36
+ <div class="sa-bundle__caption">Kompatibel mit</div>
37
+ <div class="sa-bundle__plans">{{ compatLabel(bundle) }}</div>
38
+ </q-card-section>
39
+ </q-card>
40
+ </div>
41
+ </section>
42
+ </template>
43
+
44
+ <script setup lang="ts">
45
+ import type { PlanRow } from '@saasicat/types';
46
+
47
+ interface BundleEntry {
48
+ bundleKey: string;
49
+ label?: string | null;
50
+ features: string[];
51
+ compatiblePlanKeys?: string[] | null;
52
+ }
53
+
54
+ const props = defineProps<{
55
+ bundles: BundleEntry[];
56
+ plans: PlanRow[];
57
+ featureRegistry: Record<string, { label?: string; group?: string }>;
58
+ }>();
59
+
60
+ function featureLabel(featureKey: string): string {
61
+ return props.featureRegistry[featureKey]?.label ?? featureKey;
62
+ }
63
+
64
+ function planLabel(planKey: string): string {
65
+ return props.plans.find((p) => p.planKey === planKey)?.label ?? planKey;
66
+ }
67
+
68
+ // Empty compatibility list = bundle applies to all plans (cf. PlanMatrix.hasBundle).
69
+ function compatLabel(bundle: BundleEntry): string {
70
+ const keys = bundle.compatiblePlanKeys ?? [];
71
+ if (keys.length === 0) return 'Alle Pläne';
72
+ return keys.map(planLabel).join(', ');
73
+ }
74
+ </script>
75
+
76
+ <style scoped>
77
+ .sa-bundles {
78
+ padding: 8px 16px 32px;
79
+ }
80
+ .sa-bundles__head {
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 8px;
84
+ margin: 8px 0 12px;
85
+ }
86
+ .sa-bundles__title {
87
+ font-size: 16px;
88
+ font-weight: 600;
89
+ margin: 0;
90
+ color: #0f172a;
91
+ }
92
+ .sa-bundles__count {
93
+ font-size: 12px;
94
+ color: #64748b;
95
+ background: #e2e8f0;
96
+ border-radius: 999px;
97
+ padding: 1px 8px;
98
+ }
99
+ .sa-bundles__empty {
100
+ color: #64748b;
101
+ font-size: 14px;
102
+ padding: 8px 0;
103
+ }
104
+ .sa-bundles__grid {
105
+ display: grid;
106
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
107
+ gap: 12px;
108
+ }
109
+ .sa-bundle__header {
110
+ padding-bottom: 8px;
111
+ }
112
+ .sa-bundle__label {
113
+ font-weight: 600;
114
+ color: #0f172a;
115
+ }
116
+ .sa-bundle__key {
117
+ font-size: 11px;
118
+ color: #94a3b8;
119
+ font-family: monospace;
120
+ }
121
+ .sa-bundle__caption {
122
+ font-size: 11px;
123
+ text-transform: uppercase;
124
+ letter-spacing: 0.04em;
125
+ color: #94a3b8;
126
+ margin-bottom: 4px;
127
+ }
128
+ .sa-bundle__chips {
129
+ display: flex;
130
+ flex-wrap: wrap;
131
+ gap: 4px;
132
+ }
133
+ .sa-bundle__chip {
134
+ background: #eef2ff;
135
+ color: #3730a3;
136
+ }
137
+ .sa-bundle__muted {
138
+ color: #94a3b8;
139
+ }
140
+ .sa-bundle__compat {
141
+ padding-top: 0;
142
+ }
143
+ .sa-bundle__plans {
144
+ font-size: 13px;
145
+ color: #334155;
146
+ }
147
+ </style>
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <q-dialog
3
+ :model-value="modelValue"
4
+ persistent
5
+ @update:model-value="$emit('update:modelValue', $event)"
6
+ >
7
+ <q-card style="min-width: 400px">
8
+ <q-card-section>
9
+ <div class="text-h6">Draft verwerfen?</div>
10
+ <div class="text-body2 q-mt-sm">
11
+ Draft v{{ target?.draft.version }} von
12
+ <code>{{ target?.plan.planKey }}</code> wird unwiderruflich gelöscht. Published
13
+ Versions bleiben unverändert.
14
+ </div>
15
+ </q-card-section>
16
+ <q-banner v-if="error" class="bg-warning q-mx-md q-mb-md" rounded>
17
+ {{ error }}
18
+ </q-banner>
19
+ <q-card-actions align="right">
20
+ <q-btn flat label="Abbrechen" @click="$emit('update:modelValue', false)" />
21
+ <q-btn
22
+ color="negative"
23
+ label="Verwerfen"
24
+ :loading="discarding"
25
+ @click="$emit('execute')"
26
+ />
27
+ </q-card-actions>
28
+ </q-card>
29
+ </q-dialog>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ import type { PlanDiscardTarget } from './types.js';
34
+
35
+ defineProps<{
36
+ modelValue: boolean;
37
+ target: PlanDiscardTarget | null;
38
+ error: string | null;
39
+ discarding: boolean;
40
+ }>();
41
+
42
+ defineEmits<{
43
+ (e: 'update:modelValue', value: boolean): void;
44
+ (e: 'execute'): void;
45
+ }>();
46
+ </script>
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <q-dialog
3
+ :model-value="modelValue"
4
+ persistent
5
+ @update:model-value="$emit('update:modelValue', $event)"
6
+ >
7
+ <q-card style="min-width: 420px">
8
+ <q-card-section>
9
+ <div class="text-h6">Version publishen?</div>
10
+ <div class="text-body2 q-mt-sm">
11
+ Plan <code>{{ selectedPlan?.planKey }}</code
12
+ >, Draft v{{ publishTarget?.version }}.
13
+ </div>
14
+ <div class="text-caption text-grey-7 q-mt-sm">
15
+ Eine ggf. zuvor live Version wird auf <code>supersededAt</code> gesetzt
16
+ (Vertragsschutz P1: Bestand-Subscriptions bleiben auf der alten Version bis zum
17
+ nächsten Renewal).
18
+ </div>
19
+ <q-checkbox
20
+ :model-value="forceRegressive"
21
+ label="Force-Publish auch bei Regression (Feature entfernt / Quota gesenkt / Preis erhöht)"
22
+ class="q-mt-md"
23
+ @update:model-value="$emit('update:forceRegressive', Boolean($event))"
24
+ />
25
+ <q-checkbox
26
+ :model-value="allowZeroPrice"
27
+ label="Preis 0,00 bewusst zulassen (kostenloser Sondervertrag)"
28
+ class="q-mt-sm"
29
+ @update:model-value="$emit('update:allowZeroPrice', Boolean($event))"
30
+ />
31
+ </q-card-section>
32
+ <q-banner v-if="publishError" class="bg-warning q-mx-md q-mb-md" rounded>
33
+ {{ publishError }}
34
+ </q-banner>
35
+ <q-card-section v-if="regressionChanges.length > 0" class="q-pt-none">
36
+ <div class="text-subtitle2 q-mb-xs">Regressive Änderungen:</div>
37
+ <ul class="sa-publish-regression">
38
+ <li v-for="c in regressionChanges" :key="c.field">
39
+ <strong>{{ fieldLabel(c.field) }}:</strong>
40
+ <span class="sa-publish-regression__old">
41
+ {{ formatChangeValue(c.oldValue) }}
42
+ </span>
43
+ <q-icon name="arrow_forward" size="14px" class="q-mx-xs" />
44
+ <span class="sa-publish-regression__new">
45
+ {{ formatChangeValue(c.newValue) }}
46
+ </span>
47
+ </li>
48
+ </ul>
49
+ </q-card-section>
50
+ <q-card-actions align="right">
51
+ <q-btn flat label="Abbrechen" @click="$emit('update:modelValue', false)" />
52
+ <q-btn
53
+ color="primary"
54
+ label="Publish"
55
+ :loading="publishing"
56
+ @click="$emit('execute')"
57
+ />
58
+ </q-card-actions>
59
+ </q-card>
60
+ </q-dialog>
61
+ </template>
62
+
63
+ <script setup lang="ts">
64
+ import type { PlanRow, PlanVersionRow } from '@saasicat/types';
65
+ import type { RegressionChange } from './types.js';
66
+
67
+ defineProps<{
68
+ modelValue: boolean;
69
+ selectedPlan: PlanRow | null;
70
+ publishTarget: PlanVersionRow | null;
71
+ forceRegressive: boolean;
72
+ allowZeroPrice: boolean;
73
+ publishing: boolean;
74
+ publishError: string | null;
75
+ regressionChanges: RegressionChange[];
76
+ fieldLabel: (field: string) => string;
77
+ formatChangeValue: (value: unknown) => string;
78
+ }>();
79
+
80
+ defineEmits<{
81
+ (e: 'update:modelValue', value: boolean): void;
82
+ (e: 'update:forceRegressive', value: boolean): void;
83
+ (e: 'update:allowZeroPrice', value: boolean): void;
84
+ (e: 'execute'): void;
85
+ }>();
86
+ </script>
87
+
88
+ <style scoped>
89
+ .sa-publish-regression {
90
+ margin: 0;
91
+ padding-left: 18px;
92
+ font-size: 13px;
93
+ color: #334155;
94
+ }
95
+ .sa-publish-regression li {
96
+ margin: 4px 0;
97
+ }
98
+ .sa-publish-regression__old {
99
+ color: #94a3b8;
100
+ text-decoration: line-through;
101
+ }
102
+ .sa-publish-regression__new {
103
+ color: #b91c1c;
104
+ font-weight: 600;
105
+ }
106
+ </style>
@@ -0,0 +1,69 @@
1
+ <template>
2
+ <transition name="pl-toast">
3
+ <div v-if="message" class="sa-plans__toast">
4
+ <span class="sa-plans__toast-tick" aria-hidden="true">
5
+ <svg
6
+ width="14"
7
+ height="14"
8
+ viewBox="0 0 24 24"
9
+ fill="none"
10
+ stroke="currentColor"
11
+ stroke-width="3"
12
+ >
13
+ <path d="M5 13l4 4L19 7" />
14
+ </svg>
15
+ </span>
16
+ <span>{{ message }}</span>
17
+ </div>
18
+ </transition>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ defineProps<{
23
+ message: string | null;
24
+ }>();
25
+ </script>
26
+
27
+ <style scoped>
28
+ .sa-plans__toast {
29
+ position: fixed;
30
+ bottom: 24px;
31
+ left: 50%;
32
+ transform: translateX(-50%);
33
+ background: #0f172a;
34
+ color: #fff;
35
+ padding: 12px 18px;
36
+ border-radius: 10px;
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 10px;
40
+ font:
41
+ 500 13px 'Inter',
42
+ system-ui,
43
+ sans-serif;
44
+ box-shadow: 0 12px 40px rgba(15, 23, 42, 0.3);
45
+ z-index: 6000;
46
+ max-width: 80vw;
47
+ }
48
+ .sa-plans__toast-tick {
49
+ width: 22px;
50
+ height: 22px;
51
+ border-radius: 50%;
52
+ background: #10b981;
53
+ color: #fff;
54
+ display: grid;
55
+ place-items: center;
56
+ flex: 0 0 auto;
57
+ }
58
+ .pl-toast-enter-active,
59
+ .pl-toast-leave-active {
60
+ transition:
61
+ opacity 0.22s ease,
62
+ transform 0.22s ease;
63
+ }
64
+ .pl-toast-enter-from,
65
+ .pl-toast-leave-to {
66
+ opacity: 0;
67
+ transform: translate(-50%, 12px);
68
+ }
69
+ </style>
@@ -0,0 +1,18 @@
1
+ import type { PlanRow, PlanVersionRow } from '@saasicat/types';
2
+
3
+ export interface PlanArchiveTarget {
4
+ plan: PlanRow;
5
+ hasLive: boolean;
6
+ }
7
+
8
+ export interface PlanDiscardTarget {
9
+ plan: PlanRow;
10
+ draft: PlanVersionRow;
11
+ }
12
+
13
+ export interface RegressionChange {
14
+ field: string;
15
+ oldValue: unknown;
16
+ newValue: unknown;
17
+ direction: 'REGRESSION' | 'IMPROVEMENT' | string;
18
+ }
@@ -0,0 +1,41 @@
1
+ // Types of the shared PlatformEmailPage — deliberately extracted from the `.vue` so that
2
+ // consumers can import them as regular TS types. A named type import
3
+ // directly from a `.vue` is not resolved via the `*.vue` module shim (vue-tsc);
4
+ // from this `.ts` it is (re-exported via the package index).
5
+
6
+ export interface PlatformEmailProvider {
7
+ id: string;
8
+ name: string;
9
+ smtpHost: string;
10
+ smtpPort: number;
11
+ smtpUser: string;
12
+ encryption: string;
13
+ autoTls?: boolean;
14
+ fromEmail: string;
15
+ fromName?: string | null;
16
+ isDefault: boolean;
17
+ active: boolean;
18
+ [extra: string]: unknown;
19
+ }
20
+
21
+ export interface PlatformEmailWriteInput {
22
+ name: string;
23
+ smtpHost: string;
24
+ smtpPort: number;
25
+ smtpUser: string;
26
+ smtpPassword?: string;
27
+ encryption: string;
28
+ fromEmail: string;
29
+ fromName?: string;
30
+ active?: boolean;
31
+ }
32
+
33
+ export interface PlatformEmailTestInput {
34
+ toEmail: string;
35
+ subject?: string;
36
+ }
37
+
38
+ export interface PlatformEmailTestResult {
39
+ success: boolean;
40
+ message: string;
41
+ }
@@ -0,0 +1,223 @@
1
+ /* SuperAdmin UI theme — CSS variables for the platform pages.
2
+ *
3
+ * Consumer apps can override individual
4
+ * variables via their own `:root` block in the app CSS — like
5
+ * any CSS var override. Class names follow the `sa-` prefix convention,
6
+ * i.e. an app CSS can, for example, re-style `.sa-tenants__title` precisely
7
+ * without touching the platform code.
8
+ *
9
+ */
10
+
11
+ :root {
12
+ --sa-bg-app: #f1f5f9;
13
+ --sa-bg-surface: #ffffff;
14
+ --sa-primary: #3f6bff;
15
+ --sa-primary-soft: rgba(63, 107, 255, 0.08);
16
+ --sa-primary-softer: rgba(63, 107, 255, 0.04);
17
+ --sa-primary-border: rgba(63, 107, 255, 0.18);
18
+ --sa-positive: #047857;
19
+ --sa-positive-light: #10b981;
20
+ --sa-positive-soft: rgba(4, 120, 87, 0.1);
21
+ --sa-negative: #dc2626;
22
+ --sa-negative-soft: rgba(220, 38, 38, 0.08);
23
+ --sa-warning: #b45309;
24
+ --sa-warning-soft: rgba(245, 158, 11, 0.1);
25
+ --sa-amber: #f59e0b;
26
+ --sa-amber-light: #fbbf24;
27
+ --sa-amber-border: rgba(245, 158, 11, 0.3);
28
+ --sa-heading: #0f172a;
29
+ --sa-body: #1e293b;
30
+ --sa-muted: #64748b;
31
+ --sa-muted-dark: #475569;
32
+ --sa-border: #e2e8f0;
33
+ --sa-border-soft: #f1f5f9;
34
+ --sa-font-head: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif;
35
+ --sa-font-body: 'Inter', system-ui, sans-serif;
36
+ --sa-font-mono: ui-monospace, 'SF Mono', Menlo, monospace;
37
+ }
38
+
39
+ /* ─────────────────────────────────────────────────────────────
40
+ * Shared page chrome — header strip, stat pills, toolbar, card.
41
+ * Pattern from the plan simulation; pages use it instead of creating their own
42
+ * BEM variants, so the look stays consistent across the entire
43
+ * SuperAdmin surface.
44
+ * ───────────────────────────────────────────────────────────── */
45
+
46
+ .sa-page-head {
47
+ display: flex;
48
+ align-items: flex-end;
49
+ gap: 20px;
50
+ margin-bottom: 16px;
51
+ flex-wrap: wrap;
52
+ }
53
+ .sa-page-head__title {
54
+ margin: 0;
55
+ font-family: var(--sa-font-head);
56
+ font-size: 22px;
57
+ font-weight: 700;
58
+ letter-spacing: -0.02em;
59
+ color: var(--sa-heading);
60
+ }
61
+ .sa-page-head__sub {
62
+ margin: 3px 0 0;
63
+ font-size: 12.5px;
64
+ color: var(--sa-muted);
65
+ }
66
+ .sa-page-head__actions {
67
+ margin-left: auto;
68
+ display: flex;
69
+ gap: 8px;
70
+ align-items: center;
71
+ }
72
+
73
+ /* Stat pill strip — clickable filter tiles (see pl-stat in the sim). */
74
+ .sa-stats {
75
+ display: flex;
76
+ gap: 8px;
77
+ margin: 4px 0 16px;
78
+ flex-wrap: wrap;
79
+ align-items: stretch;
80
+ }
81
+ .sa-stat {
82
+ display: inline-flex;
83
+ flex-direction: column;
84
+ align-items: flex-start;
85
+ background: var(--sa-bg-surface);
86
+ border: 1px solid var(--sa-border);
87
+ border-radius: 10px;
88
+ padding: 10px 14px 9px;
89
+ cursor: pointer;
90
+ min-width: 112px;
91
+ transition:
92
+ border-color 0.12s,
93
+ background 0.12s,
94
+ box-shadow 0.12s;
95
+ font: inherit;
96
+ color: inherit;
97
+ text-align: left;
98
+ }
99
+ .sa-stat:hover {
100
+ background: #fafbfd;
101
+ border-color: #cbd5e1;
102
+ }
103
+ .sa-stat--active {
104
+ border-color: var(--sa-heading);
105
+ box-shadow:
106
+ 0 0 0 1px var(--sa-heading) inset,
107
+ 0 2px 6px rgba(15, 23, 42, 0.06);
108
+ }
109
+ .sa-stat__num {
110
+ font-size: 22px;
111
+ font-weight: 700;
112
+ letter-spacing: -0.02em;
113
+ color: var(--sa-heading);
114
+ line-height: 1.05;
115
+ }
116
+ .sa-stat__label {
117
+ font-size: 11.5px;
118
+ font-weight: 600;
119
+ color: var(--sa-muted);
120
+ margin-top: 2px;
121
+ }
122
+ .sa-stat__hint {
123
+ font: 500 10px var(--sa-font-mono);
124
+ letter-spacing: 0.04em;
125
+ color: #94a3b8;
126
+ margin-top: 1px;
127
+ }
128
+ /* Color variants — analogous to active-state/warn-state/danger-state in the sim. */
129
+ .sa-stat--positive .sa-stat__num {
130
+ color: var(--sa-positive);
131
+ }
132
+ .sa-stat--positive.sa-stat--active {
133
+ border-color: var(--sa-positive-light);
134
+ box-shadow:
135
+ 0 0 0 1px var(--sa-positive-light) inset,
136
+ 0 2px 6px rgba(16, 185, 129, 0.1);
137
+ }
138
+ .sa-stat--warn .sa-stat__num {
139
+ color: var(--sa-warning);
140
+ }
141
+ .sa-stat--warn.sa-stat--active {
142
+ border-color: var(--sa-amber);
143
+ box-shadow:
144
+ 0 0 0 1px var(--sa-amber) inset,
145
+ 0 2px 6px rgba(245, 158, 11, 0.1);
146
+ }
147
+ .sa-stat--danger .sa-stat__num {
148
+ color: var(--sa-negative);
149
+ }
150
+ .sa-stat--danger.sa-stat--active {
151
+ border-color: var(--sa-negative);
152
+ box-shadow:
153
+ 0 0 0 1px var(--sa-negative) inset,
154
+ 0 2px 6px rgba(239, 68, 68, 0.1);
155
+ }
156
+ .sa-stat--info .sa-stat__num {
157
+ color: #1d4ed8;
158
+ }
159
+ .sa-stat--info.sa-stat--active {
160
+ border-color: #3b82f6;
161
+ box-shadow:
162
+ 0 0 0 1px #3b82f6 inset,
163
+ 0 2px 6px rgba(59, 130, 246, 0.1);
164
+ }
165
+ .sa-stat--muted .sa-stat__num {
166
+ color: var(--sa-muted-dark);
167
+ }
168
+ .sa-stat--purple .sa-stat__num {
169
+ color: #6b21a8;
170
+ }
171
+ .sa-stat--purple.sa-stat--active {
172
+ border-color: #a855f7;
173
+ box-shadow:
174
+ 0 0 0 1px #a855f7 inset,
175
+ 0 2px 6px rgba(168, 85, 247, 0.1);
176
+ }
177
+
178
+ /* Toolbar (search + filter buttons + sort). */
179
+ .sa-toolbar {
180
+ display: flex;
181
+ align-items: center;
182
+ gap: 8px;
183
+ margin-bottom: 14px;
184
+ flex-wrap: wrap;
185
+ }
186
+ .sa-toolbar__search {
187
+ flex: 1;
188
+ min-width: 220px;
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 9px;
192
+ background: var(--sa-bg-surface);
193
+ border: 1px solid var(--sa-border);
194
+ border-radius: 10px;
195
+ padding: 6px 12px;
196
+ }
197
+ .sa-toolbar__search input {
198
+ flex: 1;
199
+ border: 0;
200
+ outline: 0;
201
+ background: transparent;
202
+ font: 13px var(--sa-font-body);
203
+ color: var(--sa-body);
204
+ }
205
+ .sa-toolbar__search input::placeholder {
206
+ color: #94a3b8;
207
+ }
208
+
209
+ /* Generic card (cards in dashboard/detail sections). */
210
+ .sa-card {
211
+ background: var(--sa-bg-surface);
212
+ border: 1px solid var(--sa-border);
213
+ border-radius: 12px;
214
+ padding: 16px 18px;
215
+ }
216
+ .sa-card__title {
217
+ margin: 0 0 12px;
218
+ font-family: var(--sa-font-head);
219
+ font-size: 14px;
220
+ font-weight: 700;
221
+ color: var(--sa-heading);
222
+ letter-spacing: -0.005em;
223
+ }