@saasicat/ui-vue 0.24.2 → 0.26.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 (90) hide show
  1. package/README.md +32 -8
  2. package/dist/{catalog-DJcfm5N2.d.cts → catalog-Dch1Ryw0.d.cts} +353 -69
  3. package/dist/{catalog-DJcfm5N2.d.ts → catalog-Dch1Ryw0.d.ts} +353 -69
  4. package/dist/{chunk-F7NRM6KI.js → chunk-BPF2BMCQ.js} +748 -24
  5. package/dist/{chunk-D74FFCCP.js → chunk-NEXTZZRQ.js} +95 -177
  6. package/dist/{chunk-TQQMKWN6.js → chunk-O3J3ITF2.js} +119 -7
  7. package/dist/client/index.cjs +758 -83
  8. package/dist/client/index.d.cts +414 -9
  9. package/dist/client/index.d.ts +414 -9
  10. package/dist/client/index.js +84 -12
  11. package/dist/index.cjs +1325 -294
  12. package/dist/index.d.cts +205 -15
  13. package/dist/index.d.ts +205 -15
  14. package/dist/index.js +566 -240
  15. package/dist/quasar/index.cjs +574 -62
  16. package/dist/quasar/index.d.cts +61 -6
  17. package/dist/quasar/index.d.ts +61 -6
  18. package/dist/quasar/index.js +83 -19
  19. package/dist/{use-sa-theme-OUBlaqgl.d.ts → resource-registry-D7Xjs-5f.d.cts} +157 -7
  20. package/dist/{use-sa-theme-B5t7oXph.d.cts → resource-registry-TaR7rtq0.d.ts} +157 -7
  21. package/package.json +4 -3
  22. package/src/client/admin-error.ts +552 -0
  23. package/src/client/admin-resource-client.ts +9 -12
  24. package/src/client/batch-column-fetcher.ts +2 -2
  25. package/src/client/boot-loader.ts +12 -1
  26. package/src/client/http/axios-http-client.ts +383 -0
  27. package/src/client/http/fetch-http-client.ts +89 -0
  28. package/src/client/http/index.ts +6 -0
  29. package/src/client/http-json.ts +82 -16
  30. package/src/client/i18n/messages/discovery.ts +0 -6
  31. package/src/client/i18n/messages/errors.ts +44 -0
  32. package/src/client/i18n/messages/marketing.ts +0 -6
  33. package/src/client/i18n/messages/promos.ts +0 -2
  34. package/src/client/i18n/messages/shell.ts +12 -0
  35. package/src/client/i18n/messages.ts +3 -0
  36. package/src/client/index.ts +6 -0
  37. package/src/client/manifest-loader.ts +65 -15
  38. package/src/client/resources/audit.resource.ts +25 -0
  39. package/src/client/resources/define-resource.ts +166 -0
  40. package/src/client/resources/index.ts +42 -0
  41. package/src/client/resources/list-resource.ts +176 -0
  42. package/src/client/resources/plans.resource.ts +169 -0
  43. package/src/client/resources/resource-request.ts +117 -0
  44. package/src/client/resources/tenants.resource.ts +27 -0
  45. package/src/client/types.ts +10 -3
  46. package/src/components/BundleVersionPublishDialog.vue +1 -1
  47. package/src/components/MarketingPromotionsTab.vue +263 -241
  48. package/src/components/ThemeSwitcher.vue +85 -0
  49. package/src/components/admin-page/AdminAccordion.vue +125 -0
  50. package/src/components/bundle-editor/BundleCreatePanel.vue +1 -1
  51. package/src/components/dialogs/PromoCodeDialogFields.vue +20 -20
  52. package/src/components/plan/PlanCycleToggle.vue +1 -1
  53. package/src/components/plan-detail/PlanVersionsPanel.vue +5 -2
  54. package/src/index.ts +5 -0
  55. package/src/pages-standard/AdminLayout.vue +57 -0
  56. package/src/pages-standard/AdminManifestErrorPage.vue +4 -3
  57. package/src/pages-standard/BundlesPage.vue +5 -35
  58. package/src/pages-standard/DiscoveryPage.vue +8 -1
  59. package/src/pages-standard/MarketingCatalogPage.vue +14 -3
  60. package/src/pages-standard/SuperAdminLoginPage.vue +23 -2
  61. package/src/pages-standard/SuperAdminSetupWizard.vue +37 -3
  62. package/src/pages-standard/bundles-page/BundleAccordionList.vue +30 -32
  63. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +80 -88
  64. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +73 -79
  65. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +44 -6
  66. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +8 -2
  67. package/src/pages-tenant/PackageSnapshotPanel.vue +31 -2
  68. package/src/quasar/confirm.ts +83 -0
  69. package/src/quasar/create-super-admin-app.ts +84 -10
  70. package/src/quasar/dark-bridge.ts +47 -10
  71. package/src/quasar/index.ts +1 -0
  72. package/src/ui/theme/components/accordion.css +118 -0
  73. package/src/ui/theme/index.css +1 -0
  74. package/src/vue/create-admin-routes.ts +9 -1
  75. package/src/vue/platform-loaders.ts +12 -2
  76. package/src/vue/resource-registry.ts +211 -0
  77. package/src/vue/super-admin-context.ts +20 -5
  78. package/src/vue/ui-confirm.ts +76 -0
  79. package/src/vue/use-api-list.ts +50 -45
  80. package/src/vue/use-async-action.ts +170 -0
  81. package/src/vue/use-async-data.ts +83 -0
  82. package/src/vue/use-bundles.ts +34 -7
  83. package/src/vue/use-catalog-entries.ts +37 -12
  84. package/src/vue/use-discovery.ts +27 -6
  85. package/src/vue/use-marketing-projections.ts +29 -7
  86. package/src/vue/use-plans.ts +36 -8
  87. package/src/vue/use-promotions.ts +27 -7
  88. package/src/vue/use-resource-list.ts +272 -0
  89. package/src/vue/use-sa-theme.ts +34 -5
  90. package/src/vue/use-tenant-subscription-bundles.ts +22 -3
@@ -1,169 +1,103 @@
1
1
  import {
2
2
  DEFAULT_SA_LOCALE,
3
3
  defaultHttpClient,
4
- defaultKvStore,
4
+ filterQueryString,
5
+ isAxiosNoResponseError,
5
6
  isSaBuiltinLocale,
6
- navMessages
7
- } from "./chunk-F7NRM6KI.js";
7
+ markTransportFailure,
8
+ navMessages,
9
+ trimTrailingSlashes
10
+ } from "./chunk-BPF2BMCQ.js";
8
11
 
9
12
  // src/client/version.ts
10
13
  var ADMIN_UI_VERSION = "1.2.0";
11
14
 
12
- // src/client/http-json.ts
13
- var HttpJsonError = class extends Error {
14
- constructor(status, code) {
15
- super(code ?? `HTTP ${status}`);
16
- this.status = status;
17
- this.code = code;
18
- this.name = "HttpJsonError";
19
- }
20
- status;
21
- code;
22
- };
23
- async function extractCode(res) {
24
- try {
25
- const body = await res.json();
26
- return typeof body?.code === "string" ? body.code : void 0;
27
- } catch {
28
- return void 0;
29
- }
15
+ // src/client/http/axios-http-client.ts
16
+ function stripped(url, prefixes) {
17
+ for (const prefix of prefixes) {
18
+ const trimmed = trimTrailingSlashes(prefix);
19
+ if (!trimmed || !url.startsWith(trimmed)) continue;
20
+ const rest = url.slice(trimmed.length);
21
+ if (rest === "") return "/";
22
+ if (rest.startsWith("/")) return rest;
23
+ if (rest.startsWith("?") || rest.startsWith("#")) return `/${rest}`;
24
+ }
25
+ return url;
30
26
  }
31
- function trimTrailingSlashes(url) {
32
- let end = url.length;
33
- while (end > 0 && url[end - 1] === "/") end--;
34
- return url.slice(0, end);
27
+ function headerReader(headers) {
28
+ const record = typeof headers === "object" && headers !== null ? headers : {};
29
+ const lowered = /* @__PURE__ */ new Map();
30
+ for (const [key, value] of Object.entries(record)) lowered.set(key.toLowerCase(), value);
31
+ return (name) => {
32
+ const value = lowered.get(name.toLowerCase());
33
+ return value == null ? null : String(value);
34
+ };
35
35
  }
36
- async function getJson(http, url) {
37
- const res = await http(url);
38
- if (res.status < 200 || res.status >= 300) {
39
- throw new HttpJsonError(res.status, await extractCode(res));
40
- }
41
- return await res.json();
36
+ function bodyIsRaw(response, declared) {
37
+ if (declared !== "auto") return declared === "raw";
38
+ if (response.data === "") return true;
39
+ const config = response.config;
40
+ if (config?.transformResponse === null) return true;
41
+ if (Array.isArray(config?.transformResponse) && config.transformResponse.length === 0) {
42
+ return true;
43
+ }
44
+ const responseType = config?.responseType;
45
+ if (responseType === "json") return false;
46
+ if (typeof responseType === "string" && responseType !== "") return true;
47
+ const transitional = config?.transitional;
48
+ return typeof transitional === "object" && transitional !== null && transitional.forcedJSONParsing === false;
42
49
  }
43
- async function postJson(http, url, body) {
44
- const res = await http(url, {
45
- method: "POST",
46
- headers: { "Content-Type": "application/json" },
47
- body: JSON.stringify(body)
48
- });
49
- if (res.status < 200 || res.status >= 300) {
50
- throw new HttpJsonError(res.status, await extractCode(res));
50
+ async function readBody(data) {
51
+ if (typeof data === "string" || data === null || data === void 0) return data;
52
+ if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
53
+ if (ArrayBuffer.isView(data)) return new TextDecoder().decode(data);
54
+ const maybe = data;
55
+ if (typeof maybe.pipe === "function" || typeof maybe.getReader === "function") {
56
+ throw new TypeError(
57
+ "createAxiosHttpClient cannot read a streamed body: responseType 'stream' can be consumed only once, and never synchronously. Use 'arraybuffer' or the default for the instance the platform receives."
58
+ );
51
59
  }
52
- return await res.json();
60
+ if (typeof maybe.text === "function") return maybe.text.call(data);
61
+ return data;
53
62
  }
54
-
55
- // src/client/boot-loader.ts
56
- var BootLoadError = class extends Error {
57
- constructor(status, message) {
58
- super(message);
59
- this.status = status;
60
- this.name = "BootLoadError";
61
- }
62
- status;
63
- };
64
- var BootLoader = class {
65
- endpoint;
66
- http;
67
- constructor(options) {
68
- if (!options?.endpoint) {
69
- throw new Error(
70
- 'BootLoader: `endpoint` is required (e.g. "/api/admin/boot" or "/api/v1/admin/boot"). The platform has no default because apps use different globalPrefix conventions.'
71
- );
72
- }
73
- this.endpoint = options.endpoint;
74
- this.http = options.http ?? defaultHttpClient();
75
- }
76
- async load() {
77
- const res = await this.http(this.endpoint);
78
- if (res.status !== 200) {
79
- throw new BootLoadError(res.status, `Boot-Endpunkt antwortete HTTP ${res.status}`);
63
+ function adapt(response, declared) {
64
+ return {
65
+ status: response.status,
66
+ headers: { get: headerReader(response.headers) },
67
+ json: async () => {
68
+ const data = await readBody(response.data);
69
+ if (typeof data !== "string") return data;
70
+ return bodyIsRaw({ ...response, data }, declared) ? JSON.parse(data) : data;
71
+ },
72
+ text: async () => {
73
+ const data = await readBody(response.data);
74
+ return typeof data === "string" ? data : JSON.stringify(data);
80
75
  }
81
- return await res.json();
82
- }
83
- };
84
-
85
- // src/client/manifest-loader.ts
86
- var ManifestLoadError = class extends Error {
87
- constructor(status, message) {
88
- super(message);
89
- this.status = status;
90
- this.name = "ManifestLoadError";
91
- }
92
- status;
93
- };
94
- var ManifestLoader = class {
95
- endpoint;
96
- http;
97
- storage;
98
- bodyKey;
99
- etagKey;
100
- getAuthToken;
101
- constructor(options) {
102
- if (!options?.endpoint) {
103
- throw new Error(
104
- 'ManifestLoader: `endpoint` is required (e.g. "/api/admin/manifest" or "/api/v1/admin/manifest"). The platform has no default because apps use different globalPrefix conventions.'
76
+ };
77
+ }
78
+ function createAxiosHttpClient(instance, options = {}) {
79
+ const prefixes = typeof options.stripPrefix === "string" ? [options.stripPrefix] : options.stripPrefix ?? [];
80
+ const responseBody = options.responseBody ?? "auto";
81
+ return async (url, init) => {
82
+ try {
83
+ return adapt(
84
+ await instance.request({
85
+ url: stripped(url, prefixes),
86
+ method: (init?.method ?? "GET").toUpperCase(),
87
+ headers: init?.headers,
88
+ data: init?.body
89
+ }),
90
+ responseBody
105
91
  );
106
- }
107
- this.endpoint = options.endpoint;
108
- this.http = options.http ?? defaultHttpClient();
109
- this.storage = options.storage ?? defaultKvStore();
110
- const prefix = options.storageKeyPrefix ?? "";
111
- this.bodyKey = `${prefix}manifest:body`;
112
- this.etagKey = `${prefix}manifest:etag`;
113
- this.getAuthToken = options.getAuthToken;
114
- }
115
- /**
116
- * Loads the current manifest. On a cache hit (304) the cached
117
- * body is returned — otherwise the fresh server body.
118
- */
119
- async load() {
120
- const cachedEtag = this.storage.get(this.etagKey);
121
- const headers = {};
122
- const token = this.getAuthToken?.();
123
- if (token) headers.Authorization = `Bearer ${token}`;
124
- if (cachedEtag) headers["If-None-Match"] = cachedEtag;
125
- const res = await this.http(this.endpoint, { method: "GET", headers });
126
- if (res.status === 304) {
127
- const cachedBody = this.readCachedBody();
128
- if (!cachedBody) {
129
- throw new ManifestLoadError(
130
- 304,
131
- "Server returned 304 but the cache body is missing \u2014 call clearCache() and reload"
132
- );
92
+ } catch (err) {
93
+ const response = err?.response;
94
+ if (response && typeof response.status === "number") {
95
+ return adapt(response, responseBody);
133
96
  }
134
- return cachedBody.body;
135
- }
136
- if (res.status !== 200) {
137
- throw new ManifestLoadError(
138
- res.status,
139
- `Manifest endpoint responded with HTTP ${res.status}`
140
- );
141
- }
142
- const body = await res.json();
143
- const etag = res.headers.get("ETag") ?? res.headers.get("etag");
144
- if (etag) {
145
- this.storage.set(this.etagKey, etag);
146
- this.storage.set(this.bodyKey, JSON.stringify(body));
147
- }
148
- return body;
149
- }
150
- /** Reads the cached manifest body from storage; null if absent. */
151
- readCachedBody() {
152
- const etag = this.storage.get(this.etagKey);
153
- const bodyStr = this.storage.get(this.bodyKey);
154
- if (!etag || !bodyStr) return null;
155
- try {
156
- return { etag, body: JSON.parse(bodyStr) };
157
- } catch {
158
- return null;
97
+ throw isAxiosNoResponseError(err) ? markTransportFailure(err) : err;
159
98
  }
160
- }
161
- /** Clears the cache — e.g. on logout or after `manifest reload`. */
162
- clearCache() {
163
- this.storage.remove(this.etagKey);
164
- this.storage.remove(this.bodyKey);
165
- }
166
- };
99
+ };
100
+ }
167
101
 
168
102
  // src/client/nav-builder.ts
169
103
  var DEFAULT_STANDARD_PAGE_ROUTES = {
@@ -381,7 +315,7 @@ var ActionRegistry = class {
381
315
  // src/client/batch-column-fetcher.ts
382
316
  var BatchColumnDriftError = class extends Error {
383
317
  constructor(column, reason) {
384
- super(`Spalte "${column.key}": ${reason}`);
318
+ super(`Column "${column.key}": ${reason}`);
385
319
  this.column = column;
386
320
  this.name = "BatchColumnDriftError";
387
321
  }
@@ -424,7 +358,7 @@ var BatchColumnFetcher = class {
424
358
  const res = await this.http(url, { method: "GET", headers });
425
359
  if (res.status !== 200) {
426
360
  throw new Error(
427
- `Spalte "${column.key}" \u2014 Endpoint ${column.endpoint} antwortete HTTP ${res.status}`
361
+ `Column "${column.key}" \u2014 endpoint ${column.endpoint} responded with HTTP ${res.status}`
428
362
  );
429
363
  }
430
364
  const body = await res.json();
@@ -482,7 +416,7 @@ var BatchColumnFetcher = class {
482
416
  function createAdminResourceClient(options) {
483
417
  const base = options.adminBase ?? "/api/v1/admin";
484
418
  const tenantsEndpoint = `${base}/tenants`;
485
- const getJson2 = async (url) => {
419
+ const getJson = async (url) => {
486
420
  const response = await options.http(url, { method: "GET" });
487
421
  assertSuccess(response.status, "GET", url);
488
422
  return await response.json();
@@ -498,12 +432,12 @@ function createAdminResourceClient(options) {
498
432
  };
499
433
  return {
500
434
  tenantsEndpoint,
501
- loadTenantDetail: (slug) => getJson2(`${tenantsEndpoint}/${encodeURIComponent(slug)}`),
502
- loadUsers: (filter) => getJson2(
503
- `${base}/users${queryString({ q: filter.q, tenant: filter.tenant })}`
435
+ loadTenantDetail: (slug) => getJson(`${tenantsEndpoint}/${encodeURIComponent(slug)}`),
436
+ loadUsers: (filter) => getJson(
437
+ `${base}/users${filterQueryString({ q: filter.q, tenant: filter.tenant })}`
504
438
  ),
505
- loadAudit: (filter) => getJson2(
506
- `${base}/audit${queryString({
439
+ loadAudit: (filter) => getJson(
440
+ `${base}/audit${filterQueryString({
507
441
  actor: filter.actor,
508
442
  action: filter.action,
509
443
  entity: filter.entity,
@@ -511,9 +445,9 @@ function createAdminResourceClient(options) {
511
445
  limit: filter.limit
512
446
  })}`
513
447
  ),
514
- loadSubscriptions: () => getJson2(`${base}/subscriptions`),
515
- loadPromos: (filter) => getJson2(
516
- `${base}/promo-codes${queryString({
448
+ loadSubscriptions: () => getJson(`${base}/subscriptions`),
449
+ loadPromos: (filter) => getJson(
450
+ `${base}/promo-codes${filterQueryString({
517
451
  search: filter.search,
518
452
  status: filter.status
519
453
  })}`
@@ -538,15 +472,6 @@ function assertSuccess(status, method, url) {
538
472
  throw new Error(`${method} ${url} \u2192 HTTP ${status}`);
539
473
  }
540
474
  }
541
- function queryString(params) {
542
- const search = new URLSearchParams();
543
- for (const [key, value2] of Object.entries(params)) {
544
- if (value2 === void 0 || value2 === null || value2 === "") continue;
545
- search.set(key, String(value2));
546
- }
547
- const value = search.toString();
548
- return value ? `?${value}` : "";
549
- }
550
475
 
551
476
  // src/client/login-branding.ts
552
477
  var DEFAULT_TAG = "SuperAdmin";
@@ -676,14 +601,7 @@ function identityChipStyle(accent) {
676
601
 
677
602
  export {
678
603
  ADMIN_UI_VERSION,
679
- HttpJsonError,
680
- trimTrailingSlashes,
681
- getJson,
682
- postJson,
683
- BootLoadError,
684
- BootLoader,
685
- ManifestLoadError,
686
- ManifestLoader,
604
+ createAxiosHttpClient,
687
605
  DEFAULT_STANDARD_PAGE_ROUTES,
688
606
  defaultSectionOrder,
689
607
  buildRoutes,
@@ -1,7 +1,10 @@
1
1
  import {
2
+ BootLoader,
3
+ ManifestLoader,
4
+ bindResource,
2
5
  createSaCatalog,
3
6
  defaultKvStore
4
- } from "./chunk-F7NRM6KI.js";
7
+ } from "./chunk-BPF2BMCQ.js";
5
8
 
6
9
  // src/vue/super-admin-context.ts
7
10
  var SUPER_ADMIN_BRAND_KEY = /* @__PURE__ */ Symbol.for(
@@ -73,6 +76,11 @@ var SUPER_ADMIN_NOTIFY_KEY = /* @__PURE__ */ Symbol.for(
73
76
  "@saasicat/ui-vue/SUPER_ADMIN_NOTIFY"
74
77
  );
75
78
 
79
+ // src/vue/ui-confirm.ts
80
+ var SUPER_ADMIN_CONFIRM_KEY = /* @__PURE__ */ Symbol.for(
81
+ "@saasicat/ui-vue/SUPER_ADMIN_CONFIRM"
82
+ );
83
+
76
84
  // src/vue/use-super-admin-i18n.ts
77
85
  import {
78
86
  computed,
@@ -129,14 +137,15 @@ function useSaMessages(namespace) {
129
137
  import {
130
138
  computed as computed2,
131
139
  inject as inject2,
140
+ isReadonly as isReadonly2,
132
141
  isRef as isRef2,
133
142
  ref as ref2,
134
143
  watch as watch2
135
144
  } from "vue";
136
- var SCHEMES = ["light", "dark", "system"];
137
- var STORAGE_KEY = "saasicat.theme.scheme";
145
+ var SA_COLOR_SCHEMES = ["light", "dark", "system"];
146
+ var SA_THEME_STORAGE_KEY = "saasicat.theme.scheme";
138
147
  function isScheme(value) {
139
- return typeof value === "string" && SCHEMES.includes(value);
148
+ return typeof value === "string" && SA_COLOR_SCHEMES.includes(value);
140
149
  }
141
150
  function systemScheme() {
142
151
  const value = ref2("light");
@@ -157,13 +166,19 @@ function createSaTheme(options = {}) {
157
166
  const persist = options.persist !== false;
158
167
  const provided = options.scheme;
159
168
  const appOwnsIt = isRef2(provided);
160
- const stored = persist && !appOwnsIt ? storage.get(STORAGE_KEY) : null;
169
+ const key = `${options.storageKeyPrefix ?? ""}${SA_THEME_STORAGE_KEY}`;
170
+ const stored = persist && !appOwnsIt ? storage.get(key) : null;
161
171
  const scheme = isRef2(provided) ? provided : ref2(isScheme(stored) ? stored : provided ?? "system");
162
172
  const { value: system, unsubscribe } = systemScheme();
163
- const stopPersisting = persist && !appOwnsIt ? watch2(scheme, (next) => storage.set(STORAGE_KEY, next)) : null;
173
+ const stopPersisting = persist && !appOwnsIt ? watch2(scheme, (next) => storage.set(key, next)) : null;
164
174
  return {
165
175
  scheme,
166
176
  resolved: computed2(() => scheme.value === "system" ? system.value : scheme.value),
177
+ // A readonly ref (typically a `computed` derived from a profile
178
+ // setting) silently swallows writes, so the switcher would be a dead
179
+ // control. TypeScript ignores `readonly` when checking assignability,
180
+ // so the guard has to be a runtime one — same as the locale next door.
181
+ switcherEnabled: (options.switcher ?? true) && !isReadonly2(scheme),
167
182
  dispose: () => {
168
183
  unsubscribe();
169
184
  stopPersisting?.();
@@ -179,6 +194,95 @@ function useSaTheme() {
179
194
  return fallbackTheme;
180
195
  }
181
196
 
197
+ // src/vue/resource-registry.ts
198
+ import { inject as inject3 } from "vue";
199
+ function boundWithOverride(def, http, readContext, override) {
200
+ const context = override?.context ? () => ({ ...readContext(), ...override.context }) : readContext;
201
+ const platform = bindResource(def, override?.http ?? http, context);
202
+ if (!override?.ops) return platform;
203
+ const result = { ...platform };
204
+ for (const [name, wrap] of Object.entries(override.ops)) {
205
+ if (typeof wrap !== "function") continue;
206
+ if (!Object.hasOwn(platform, name)) {
207
+ throw new Error(
208
+ `createResourceRegistry: resource "${def.key}" has no operation "${name}" to override. It offers: ${Object.keys(platform).join(", ")}.`
209
+ );
210
+ }
211
+ const next = platform[name];
212
+ result[name] = (...args) => wrap(next, ...args);
213
+ }
214
+ return result;
215
+ }
216
+ function createResourceRegistry(options) {
217
+ if (!options?.http) {
218
+ throw new Error(
219
+ "createResourceRegistry: `http` is required. Pass the HttpClient your app already uses \u2014 createAxiosHttpClient(instance) or createFetchHttpClient({ headers }) \u2014 so every platform request carries its auth."
220
+ );
221
+ }
222
+ const readContext = typeof options.context === "function" ? options.context : () => options.context;
223
+ const unknown = Object.keys(options.overrides ?? {}).filter(
224
+ (key) => !Object.hasOwn(options.resources, key)
225
+ );
226
+ if (unknown.length > 0) {
227
+ throw new Error(
228
+ `createResourceRegistry: no resource named ${unknown.map((k) => `"${k}"`).join(", ")} to override. The registry holds: ${Object.keys(options.resources).join(", ")}.`
229
+ );
230
+ }
231
+ const bound = /* @__PURE__ */ new Map();
232
+ for (const [key, def] of Object.entries(options.resources)) {
233
+ bound.set(key, boundWithOverride(def, options.http, readContext, options.overrides?.[key]));
234
+ }
235
+ return {
236
+ get(key) {
237
+ const ops = bound.get(key);
238
+ if (!ops) {
239
+ throw new Error(
240
+ `useResource("${String(key)}"): no such resource. The registry offers: ${[...bound.keys()].join(", ")}.`
241
+ );
242
+ }
243
+ return ops;
244
+ },
245
+ keys: () => [...bound.keys()]
246
+ };
247
+ }
248
+ var SUPER_ADMIN_RESOURCES_KEY = /* @__PURE__ */ Symbol.for(
249
+ "@saasicat/ui-vue/SUPER_ADMIN_RESOURCES"
250
+ );
251
+ function useResource(key) {
252
+ const registry = inject3(SUPER_ADMIN_RESOURCES_KEY, null);
253
+ if (!registry) {
254
+ throw new Error(
255
+ `useResource("${key}"): no resource registry in scope. Either the component was not mounted inside createSuperAdminApp(), or the app did not pass an \`http\` client \u2014 the registry is not installed without one, because a bare fetch would send every request without your Authorization header and fail silently.`
256
+ );
257
+ }
258
+ return registry.get(key);
259
+ }
260
+
261
+ // src/vue/platform-loaders.ts
262
+ function resolveSuperAdminEndpoints(endpoints) {
263
+ return {
264
+ apiBase: endpoints.apiBase,
265
+ publicBootEndpoint: endpoints.publicBootEndpoint ?? `${endpoints.apiBase}/boot`,
266
+ manifestEndpoint: endpoints.manifestEndpoint ?? `${endpoints.apiBase}/manifest`,
267
+ projectKey: endpoints.projectKey ?? ""
268
+ };
269
+ }
270
+ function createPlatformLoaders(options) {
271
+ const resolved = resolveSuperAdminEndpoints(options.endpoints);
272
+ const bootLoader = new BootLoader({
273
+ endpoint: resolved.publicBootEndpoint,
274
+ http: options.http
275
+ });
276
+ const manifestLoader = new ManifestLoader({
277
+ endpoint: resolved.manifestEndpoint,
278
+ http: options.http,
279
+ storage: options.storage,
280
+ storageKeyPrefix: options.storageKeyPrefix,
281
+ getAuthToken: options.getAuthToken
282
+ });
283
+ return { bootLoader, manifestLoader };
284
+ }
285
+
182
286
  export {
183
287
  SUPER_ADMIN_BRAND_KEY,
184
288
  SUPER_ADMIN_ENDPOINTS_KEY,
@@ -190,12 +294,20 @@ export {
190
294
  SUPER_ADMIN_HTTP_KEY,
191
295
  buildNavigationGuard,
192
296
  SUPER_ADMIN_NOTIFY_KEY,
297
+ SUPER_ADMIN_CONFIRM_KEY,
193
298
  SA_LOCALE_STORAGE_KEY,
194
299
  SUPER_ADMIN_I18N_KEY,
195
300
  createSuperAdminI18n,
196
301
  useSuperAdminI18n,
197
302
  useSaMessages,
303
+ SA_COLOR_SCHEMES,
304
+ SA_THEME_STORAGE_KEY,
198
305
  createSaTheme,
199
306
  SA_THEME_KEY,
200
- useSaTheme
307
+ useSaTheme,
308
+ createResourceRegistry,
309
+ SUPER_ADMIN_RESOURCES_KEY,
310
+ useResource,
311
+ resolveSuperAdminEndpoints,
312
+ createPlatformLoaders
201
313
  };