@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,411 @@
1
+ <template>
2
+ <q-layout view="lHh Lpr lFf">
3
+ <div v-if="isProduction" class="sa-admin-banner sa-admin-banner--prod">
4
+ <q-icon name="warning" size="14px" />
5
+ <strong>PRODUCTION</strong> — Aktionen wirken sich sofort auf alle Mandanten aus.
6
+ </div>
7
+
8
+ <q-header elevated class="sa-admin-header">
9
+ <q-toolbar class="q-py-sm">
10
+ <q-btn flat dense round icon="menu" @click="leftDrawerOpen = !leftDrawerOpen" />
11
+ <q-toolbar-title class="text-weight-bold">
12
+ {{ currentPageTitle }}
13
+ <div class="sa-admin-header__sub">SuperAdmin · Plattform-Verwaltung</div>
14
+ </q-toolbar-title>
15
+ <q-space />
16
+ <slot name="header-actions" />
17
+ <q-badge class="sa-admin-badge q-mr-sm">SUPER ADMIN</q-badge>
18
+ <div class="sa-admin-user">
19
+ <q-avatar size="32px" class="sa-admin-user__avatar">
20
+ {{ initials }}
21
+ </q-avatar>
22
+ <div class="column items-start q-mr-sm sa-admin-user__name">
23
+ <div class="text-body2 text-weight-semibold">{{ userName }}</div>
24
+ <div class="text-caption sa-admin-user__email">{{ userEmail }}</div>
25
+ </div>
26
+ <q-btn flat round dense icon="logout" size="sm" @click="emit('logout')">
27
+ <q-tooltip>Abmelden</q-tooltip>
28
+ </q-btn>
29
+ </div>
30
+ </q-toolbar>
31
+ </q-header>
32
+
33
+ <q-drawer
34
+ v-model="leftDrawerOpen"
35
+ show-if-above
36
+ bordered
37
+ :width="240"
38
+ class="sa-admin-drawer"
39
+ >
40
+ <div class="sa-admin-drawer__stack">
41
+ <div class="sa-admin-drawer__brand">
42
+ <div class="sa-admin-drawer__logo">{{ brandLogoText }}</div>
43
+ <div>
44
+ <div class="sa-admin-drawer__brand-name">{{ brandName }}</div>
45
+ <div class="sa-admin-drawer__brand-tag">{{ brandTag }} v{{ adminUiVersion }}</div>
46
+ </div>
47
+ </div>
48
+
49
+ <q-list class="sa-admin-drawer__list">
50
+ <template v-for="section in navSections" :key="section.title ?? '__default'">
51
+ <div v-if="section.title" class="sa-admin-drawer__section">
52
+ {{ section.title }}
53
+ </div>
54
+ <q-item
55
+ v-for="item in section.items"
56
+ :key="item.to"
57
+ v-ripple
58
+ clickable
59
+ :to="item.to"
60
+ :exact="item.exact"
61
+ active-class="sa-admin-drawer__item--active"
62
+ >
63
+ <q-item-section avatar><q-icon :name="item.icon" /></q-item-section>
64
+ <q-item-section>{{ item.label }}</q-item-section>
65
+ </q-item>
66
+ </template>
67
+ </q-list>
68
+
69
+ <div v-if="docUrl || $slots['drawer-footer']" class="sa-admin-drawer__footer">
70
+ <slot name="drawer-footer">
71
+ <a
72
+ v-if="docUrl"
73
+ class="sa-admin-drawer__doc"
74
+ :href="docUrl"
75
+ target="_blank"
76
+ rel="noopener"
77
+ >
78
+ <q-icon name="menu_book" size="14px" /> Doku öffnen
79
+ </a>
80
+ </slot>
81
+ </div>
82
+ </div>
83
+ </q-drawer>
84
+
85
+ <q-page-container>
86
+ <div :class="['sa-admin-content', { 'sa-admin-content--fullbleed': isFullbleed }]">
87
+ <router-view />
88
+ </div>
89
+ </q-page-container>
90
+ </q-layout>
91
+ </template>
92
+
93
+ <script setup lang="ts">
94
+ import { computed, ref } from 'vue';
95
+ import { useRoute } from 'vue-router';
96
+ import type { AdminManifest, StandardPageKey } from '@saasicat/types';
97
+ import { buildRoutes, buildSidebar, DEFAULT_SECTION_ORDER } from '../nav-builder.js';
98
+ import { ADMIN_UI_VERSION } from '../version.js';
99
+
100
+ // SuperAdmin layout — universal platform shell for all consumer apps.
101
+ // CSS classes `sa-admin-*` with BEM convention; apps can override any class
102
+ // with equal specificity in their own CSS.
103
+ //
104
+ // App-specific bits via props:
105
+ // - `brandLogoText` : 2-letter abbreviation (e.g. 'ma', 'da')
106
+ // - `brandName` : 'DemoApp' / 'ClubApp' / …
107
+ // - `brandTag` : 'SuperAdmin' (default)
108
+ // - `manifest` : loaded AdminManifest (or null before auth)
109
+ // - `staticNavFallback` : sidebar before manifest load (no flicker)
110
+ // - `localItems` : additional sidebar items not present in the
111
+ // manifest (e.g. an app-specific "Plan-Versionen" page)
112
+ // - `availableExtensions` : set for ProjectPage capability filter
113
+ // - `standardPageRoutes`: override per StandardPageKey (e.g.
114
+ // `dashboard` → '/admin/' instead of '/admin/dashboard')
115
+ // - `pageTitleResolver` : (path) => string mapping for the header title
116
+ // - `userName` / `userEmail` / `userInitials` : display
117
+ // - `isProduction` : shows red production banner
118
+ // - `docUrl` : optional footer link
119
+ //
120
+ // Slots:
121
+ // - `#header-actions` : additional buttons on the right before the badge
122
+ // (e.g. NotificationBell)
123
+ // - `#drawer-footer` : custom footer (overrides default doc link)
124
+
125
+ export interface SidebarItem {
126
+ to: string;
127
+ icon: string;
128
+ label: string;
129
+ exact?: boolean;
130
+ }
131
+
132
+ const props = withDefaults(
133
+ defineProps<{
134
+ brandLogoText: string;
135
+ brandName: string;
136
+ brandTag?: string;
137
+ manifest?: AdminManifest | null;
138
+ staticNavFallback?: readonly SidebarItem[];
139
+ localItems?: readonly SidebarItem[];
140
+ /**
141
+ * Section label under which `localItems` are grouped in the drawer.
142
+ * Default `null` ⇒ without section header (pinned at the top, as before).
143
+ */
144
+ localItemsSection?: string | null;
145
+ availableExtensions?: Set<string>;
146
+ standardPageRoutes?: Partial<Record<StandardPageKey, string>>;
147
+ /**
148
+ * Override for `navSection` per StandardPage. The platform default follows
149
+ * the plan-simulation layout (Übersicht / Produktkatalog / Kunden /
150
+ * System) and usually needs no adjustment.
151
+ */
152
+ standardPageNavSection?: Partial<Record<StandardPageKey, string>>;
153
+ /**
154
+ * Order of the section headers. Sections not listed here are
155
+ * appended alphabetically afterwards.
156
+ */
157
+ sectionOrder?: readonly string[];
158
+ pageTitleResolver?: (path: string) => string | null;
159
+ userName?: string;
160
+ userEmail?: string;
161
+ userInitials?: string;
162
+ isProduction?: boolean;
163
+ docUrl?: string;
164
+ adminPathPrefix?: string;
165
+ }>(),
166
+ {
167
+ brandTag: 'SuperAdmin',
168
+ staticNavFallback: () => [],
169
+ localItems: () => [],
170
+ localItemsSection: null,
171
+ adminPathPrefix: '/admin',
172
+ isProduction: false,
173
+ sectionOrder: () => DEFAULT_SECTION_ORDER,
174
+ },
175
+ );
176
+
177
+ const emit = defineEmits<{
178
+ (e: 'logout'): void;
179
+ }>();
180
+
181
+ const route = useRoute();
182
+ const leftDrawerOpen = ref(false);
183
+
184
+ const adminUiVersion = ADMIN_UI_VERSION;
185
+
186
+ const initials = computed(() => props.userInitials ?? '');
187
+ const userName = computed(() => props.userName ?? '');
188
+ const userEmail = computed(() => props.userEmail ?? '');
189
+
190
+ const isFullbleed = computed(() => route.meta.fullbleed === true);
191
+
192
+ interface NavSection {
193
+ title: string | null;
194
+ items: SidebarItem[];
195
+ }
196
+
197
+ const navSections = computed<NavSection[]>(() => {
198
+ const m = props.manifest;
199
+ if (!m) {
200
+ // Pre-manifest: static fallback + local items as one unnamed
201
+ // section, so that the UI has no flicker before manifest load.
202
+ return [{ title: null, items: [...props.staticNavFallback, ...props.localItems] }];
203
+ }
204
+ const routes = buildRoutes(m, {
205
+ standardPageRoutes: props.standardPageRoutes,
206
+ standardPageNavSection: props.standardPageNavSection,
207
+ availableExtensions: props.availableExtensions,
208
+ });
209
+ const sections = buildSidebar(routes, props.sectionOrder);
210
+ const result: NavSection[] = sections.map((s) => ({
211
+ title: s.section,
212
+ items: s.items.map((item) => ({
213
+ to: item.path,
214
+ icon: item.icon ?? 'circle',
215
+ label: item.label,
216
+ exact: item.path === props.adminPathPrefix || item.path === `${props.adminPathPrefix}/`,
217
+ })),
218
+ }));
219
+ if (props.localItems.length > 0) {
220
+ const target = props.localItemsSection;
221
+ if (target === null) {
222
+ result.push({ title: null, items: [...props.localItems] });
223
+ } else {
224
+ const existing = result.find((s) => s.title === target);
225
+ if (existing) {
226
+ existing.items.push(...props.localItems);
227
+ } else {
228
+ result.push({ title: target, items: [...props.localItems] });
229
+ }
230
+ }
231
+ }
232
+ return result;
233
+ });
234
+
235
+ const resolvedNav = computed<SidebarItem[]>(() => navSections.value.flatMap((s) => s.items));
236
+
237
+ const currentPageTitle = computed(() => {
238
+ if (props.pageTitleResolver) {
239
+ const t = props.pageTitleResolver(route.path);
240
+ if (t) return t;
241
+ }
242
+ // Default: sidebar item label that matches the active route.
243
+ const item = resolvedNav.value.find((i) => i.to === route.path);
244
+ return item?.label ?? `${props.brandName} SuperAdmin`;
245
+ });
246
+ </script>
247
+
248
+ <style scoped>
249
+ .sa-admin-banner {
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ gap: 8px;
254
+ padding: 6px 12px;
255
+ font-size: 12px;
256
+ letter-spacing: 0.05em;
257
+ text-transform: uppercase;
258
+ }
259
+ .sa-admin-banner--prod {
260
+ background: linear-gradient(90deg, #b91c1c, #dc2626);
261
+ color: #fff;
262
+ }
263
+
264
+ .sa-admin-header {
265
+ background: var(--sa-admin-header-bg, linear-gradient(90deg, #0f172a, #1e293b));
266
+ color: var(--sa-admin-header-fg, #fff);
267
+ }
268
+ .sa-admin-header__sub {
269
+ font-size: 11px;
270
+ color: var(--sa-admin-header-sub, #fbbf24);
271
+ font-weight: 400;
272
+ }
273
+
274
+ .sa-admin-badge {
275
+ background: var(--sa-admin-badge-bg, #f59e0b);
276
+ color: var(--sa-admin-badge-fg, #0f172a);
277
+ font-weight: 800;
278
+ letter-spacing: 0.08em;
279
+ font-size: 10px;
280
+ padding: 4px 8px;
281
+ }
282
+
283
+ .sa-admin-user {
284
+ display: flex;
285
+ align-items: center;
286
+ gap: 8px;
287
+ }
288
+ .sa-admin-user__avatar {
289
+ background: var(--sa-admin-user-avatar-bg, #f59e0b);
290
+ color: var(--sa-admin-user-avatar-fg, #fff);
291
+ }
292
+ .sa-admin-user__name {
293
+ line-height: 1.05;
294
+ }
295
+ .sa-admin-user__email {
296
+ color: var(--sa-admin-user-email, #fbbf24);
297
+ }
298
+
299
+ .sa-admin-drawer :deep(.q-drawer),
300
+ .sa-admin-drawer :deep(.q-drawer__content),
301
+ .sa-admin-drawer__stack {
302
+ background: var(--sa-admin-drawer-bg, #0b1220);
303
+ color: var(--sa-admin-drawer-fg, #f1f5f9);
304
+ }
305
+ .sa-admin-drawer__stack {
306
+ display: flex;
307
+ flex-direction: column;
308
+ height: 100%;
309
+ }
310
+
311
+ .sa-admin-drawer__brand {
312
+ display: flex;
313
+ align-items: center;
314
+ gap: 12px;
315
+ padding: 18px 16px;
316
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
317
+ }
318
+ .sa-admin-drawer__logo {
319
+ width: 36px;
320
+ height: 36px;
321
+ border-radius: 10px;
322
+ background: var(--sa-admin-drawer-logo-bg, linear-gradient(135deg, #f59e0b, #d97706));
323
+ color: var(--sa-admin-drawer-logo-fg, #0f172a);
324
+ font-weight: 800;
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: center;
328
+ }
329
+ .sa-admin-drawer__brand-name {
330
+ font-weight: 800;
331
+ color: #fff;
332
+ }
333
+ .sa-admin-drawer__brand-tag {
334
+ font-size: 11px;
335
+ color: var(--sa-admin-drawer-brand-tag, #fbbf24);
336
+ text-transform: uppercase;
337
+ letter-spacing: 0.08em;
338
+ }
339
+
340
+ .sa-admin-drawer__list {
341
+ flex: 1;
342
+ padding: 6px 4px;
343
+ }
344
+ .sa-admin-drawer__section {
345
+ font-size: 10px;
346
+ text-transform: uppercase;
347
+ letter-spacing: 0.12em;
348
+ color: var(--sa-admin-drawer-section-fg, #64748b);
349
+ padding: 14px 18px 6px;
350
+ font-weight: 600;
351
+ }
352
+ .sa-admin-drawer__list :deep(.q-item) {
353
+ color: var(--sa-admin-drawer-item-fg, #cbd5e1);
354
+ border-radius: 7px;
355
+ margin: 1px 6px;
356
+ min-height: 36px;
357
+ padding: 6px 12px;
358
+ font-size: 13.5px;
359
+ font-weight: 500;
360
+ }
361
+ .sa-admin-drawer__list :deep(.q-item__section--avatar) {
362
+ min-width: 28px;
363
+ padding-right: 8px;
364
+ }
365
+ .sa-admin-drawer__list :deep(.q-item .q-icon) {
366
+ color: #e2e8f0;
367
+ }
368
+ .sa-admin-drawer__list :deep(.q-item:hover) {
369
+ background: var(--sa-admin-drawer-hover-bg, rgba(245, 158, 11, 0.15));
370
+ color: var(--sa-admin-drawer-hover-fg, #fbbf24);
371
+ }
372
+ .sa-admin-drawer__list :deep(.q-item:hover .q-icon) {
373
+ color: var(--sa-admin-drawer-hover-fg, #fbbf24);
374
+ }
375
+ .sa-admin-drawer__list :deep(.sa-admin-drawer__item--active) {
376
+ background: var(--sa-admin-drawer-active-bg, rgba(245, 158, 11, 0.22)) !important;
377
+ color: var(--sa-admin-drawer-active-fg, #fbbf24) !important;
378
+ font-weight: 600;
379
+ }
380
+ .sa-admin-drawer__list :deep(.sa-admin-drawer__item--active .q-icon) {
381
+ color: var(--sa-admin-drawer-active-fg, #fbbf24);
382
+ }
383
+
384
+ .sa-admin-drawer__footer {
385
+ padding: 12px 16px;
386
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
387
+ font-size: 11px;
388
+ color: #cbd5e1;
389
+ }
390
+ .sa-admin-drawer__doc {
391
+ display: inline-flex;
392
+ align-items: center;
393
+ gap: 6px;
394
+ margin-top: 6px;
395
+ color: #e2e8f0;
396
+ text-decoration: none;
397
+ }
398
+ .sa-admin-drawer__doc:hover {
399
+ color: var(--sa-admin-drawer-hover-fg, #fbbf24);
400
+ }
401
+
402
+ .sa-admin-content {
403
+ max-width: 1600px;
404
+ margin: 0 auto;
405
+ padding: 16px 24px 32px;
406
+ }
407
+ .sa-admin-content--fullbleed {
408
+ max-width: none;
409
+ padding: 0;
410
+ }
411
+ </style>
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <div class="sa-manifest-error">
3
+ <q-card class="sa-manifest-error__card">
4
+ <q-card-section class="sa-manifest-error__head">
5
+ <q-icon name="cloud_off" size="32px" color="negative" />
6
+ <h1 class="sa-manifest-error__title">Admin-Manifest nicht erreichbar</h1>
7
+ </q-card-section>
8
+ <q-card-section>
9
+ <p class="sa-manifest-error__lead">
10
+ Das Plattform-Manifest konnte nicht geladen werden. Ohne Manifest sind Sidebar,
11
+ Capabilities und Action-Buttons nicht zuverlässig — die UI würde sonst falsche
12
+ oder unerreichbare Buttons rendern. Der Router hat dich deshalb hierher
13
+ geschickt (fail-closed).
14
+ </p>
15
+ <p v-if="errorMessage" class="sa-manifest-error__detail">
16
+ <strong>Detail:</strong> {{ errorMessage }}
17
+ </p>
18
+ <div class="sa-manifest-error__actions">
19
+ <q-btn unelevated color="primary" label="Erneut laden" @click="onRetry" />
20
+ <q-btn flat label="Logout" @click="onLogout" />
21
+ </div>
22
+ </q-card-section>
23
+ </q-card>
24
+ </div>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ // Platform standard error page for `manifestGuard.errorRoute`.
29
+ // Consumers pass `onRetry`/`onLogout` in as callbacks and decide
30
+ // themselves what happens (e.g. manifestStore.clearCache() + ensureLoaded,
31
+ // authStore.logout, router.replace).
32
+
33
+ defineProps<{
34
+ errorMessage?: string | null;
35
+ onRetry: () => Promise<void> | void;
36
+ onLogout: () => void;
37
+ }>();
38
+ </script>
39
+
40
+ <style scoped>
41
+ .sa-manifest-error {
42
+ padding: 32px;
43
+ display: flex;
44
+ justify-content: center;
45
+ }
46
+ .sa-manifest-error__card {
47
+ max-width: 640px;
48
+ width: 100%;
49
+ }
50
+ .sa-manifest-error__head {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 12px;
54
+ }
55
+ .sa-manifest-error__title {
56
+ margin: 0;
57
+ font-size: 22px;
58
+ }
59
+ .sa-manifest-error__lead,
60
+ .sa-manifest-error__detail {
61
+ margin: 0 0 12px;
62
+ color: #475569;
63
+ line-height: 1.6;
64
+ }
65
+ .sa-manifest-error__detail {
66
+ background: #fef2f2;
67
+ border-left: 3px solid #dc2626;
68
+ padding: 8px 12px;
69
+ border-radius: 4px;
70
+ font-family: ui-monospace, SFMono-Regular, monospace;
71
+ font-size: 13px;
72
+ color: #991b1b;
73
+ }
74
+ .sa-manifest-error__actions {
75
+ display: flex;
76
+ gap: 8px;
77
+ margin-top: 16px;
78
+ }
79
+ </style>