@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
@@ -23,6 +23,7 @@ __export(client_exports, {
23
23
  ADMIN_UI_VERSION: () => ADMIN_UI_VERSION,
24
24
  ActionDefNotInManifestError: () => ActionDefNotInManifestError,
25
25
  ActionRegistry: () => ActionRegistry,
26
+ AdminError: () => AdminError,
26
27
  BatchColumnDriftError: () => BatchColumnDriftError,
27
28
  BatchColumnFetcher: () => BatchColumnFetcher,
28
29
  BootLoadError: () => BootLoadError,
@@ -34,6 +35,10 @@ __export(client_exports, {
34
35
  IDENTITY_ACCENT_VALUES: () => IDENTITY_ACCENT_VALUES,
35
36
  IDENTITY_NEUTRAL: () => IDENTITY_NEUTRAL,
36
37
  IDENTITY_NEUTRAL_VALUE: () => IDENTITY_NEUTRAL_VALUE,
38
+ LIST_FIRST_PAGE: () => LIST_FIRST_PAGE,
39
+ LIST_PAGE_SIZE_DEFAULT: () => LIST_PAGE_SIZE_DEFAULT,
40
+ LIST_PAGE_SIZE_MAX: () => LIST_PAGE_SIZE_MAX,
41
+ LIST_PAGINATION_PARAMS: () => LIST_PAGINATION_PARAMS,
37
42
  ManifestLoadError: () => ManifestLoadError,
38
43
  ManifestLoader: () => ManifestLoader,
39
44
  MissingHandlerError: () => MissingHandlerError,
@@ -41,32 +46,63 @@ __export(client_exports, {
41
46
  SA_LOCALES: () => SA_LOCALES,
42
47
  SA_LOCALE_LABELS: () => SA_LOCALE_LABELS,
43
48
  SA_MESSAGES: () => SA_MESSAGES,
49
+ adminErrorMessage: () => adminErrorMessage,
50
+ auditResource: () => auditResource,
51
+ bindResource: () => bindResource,
44
52
  buildRoutes: () => buildRoutes,
45
53
  buildSidebar: () => buildSidebar,
54
+ clampListPage: () => clampListPage,
55
+ clampListPageSize: () => clampListPageSize,
46
56
  countPlans: () => countPlans,
47
57
  createAdminResourceClient: () => createAdminResourceClient,
58
+ createAxiosHttpClient: () => createAxiosHttpClient,
59
+ createFetchHttpClient: () => createFetchHttpClient,
48
60
  createSaCatalog: () => createSaCatalog,
49
61
  defaultHttpClient: () => defaultHttpClient,
50
62
  defaultKvStore: () => defaultKvStore,
51
63
  defaultSectionOrder: () => defaultSectionOrder,
64
+ defineListOp: () => defineListOp,
52
65
  defineMessages: () => defineMessages,
66
+ defineResource: () => defineResource,
67
+ filterQueryString: () => filterQueryString,
53
68
  formatCurrency: () => formatCurrency,
54
69
  formatMessage: () => formatMessage,
55
70
  getJson: () => getJson,
56
71
  identityAccentAt: () => identityAccentAt,
57
72
  identityAccentFor: () => identityAccentFor,
58
73
  identityChipStyle: () => identityChipStyle,
74
+ isAdminError: () => isAdminError,
75
+ isAxiosNoResponseError: () => isAxiosNoResponseError,
59
76
  isCurrentlyValid: () => isCurrentlyValid,
77
+ isEmptyResponse: () => isEmptyResponse,
60
78
  isExpired: () => isExpired,
61
79
  isFutureScheduled: () => isFutureScheduled,
80
+ isPlatformError: () => isPlatformError,
62
81
  isProductionBoot: () => isProductionBoot,
63
82
  isSaBuiltinLocale: () => isSaBuiltinLocale,
83
+ isSentInQuery: () => isSentInQuery,
84
+ isTransportFailure: () => isTransportFailure,
85
+ listUrl: () => listUrl,
86
+ markEmptyResponse: () => markEmptyResponse,
87
+ markPlatformError: () => markPlatformError,
88
+ markTransportFailure: () => markTransportFailure,
64
89
  mergeMessages: () => mergeMessages,
90
+ planVersionsResource: () => planVersionsResource,
91
+ plansResource: () => plansResource,
92
+ platformResources: () => platformResources,
65
93
  postJson: () => postJson,
94
+ readErrorCode: () => readErrorCode,
95
+ readErrorDetail: () => readErrorDetail,
96
+ readListPage: () => readListPage,
97
+ requestJson: () => requestJson,
98
+ requestJsonBody: () => requestJsonBody,
99
+ requireServerAnswer: () => requireServerAnswer,
66
100
  resolveExtension: () => resolveExtension,
67
101
  resolveLoginBranding: () => resolveLoginBranding,
68
102
  resolveMessages: () => resolveMessages,
69
103
  resolvePlans: () => resolvePlans,
104
+ tenantsResource: () => tenantsResource,
105
+ toAdminError: () => toAdminError,
70
106
  todayIsoDate: () => todayIsoDate,
71
107
  trimTrailingSlashes: () => trimTrailingSlashes
72
108
  });
@@ -75,6 +111,287 @@ module.exports = __toCommonJS(client_exports);
75
111
  // src/client/version.ts
76
112
  var ADMIN_UI_VERSION = "1.2.0";
77
113
 
114
+ // src/client/i18n/format.ts
115
+ var import_types = require("@saasicat/types");
116
+ function formatMessage(template, params) {
117
+ return (0, import_types.formatErrorMessage)(template, params);
118
+ }
119
+
120
+ // src/client/admin-error.ts
121
+ var ADMIN_ERROR = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/AdminError");
122
+ var PLATFORM_ERROR = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/PlatformError");
123
+ function markPlatformError(error) {
124
+ Object.defineProperty(error, PLATFORM_ERROR, { value: true });
125
+ }
126
+ function isPlatformError(value) {
127
+ return typeof value === "object" && value !== null && PLATFORM_ERROR in value;
128
+ }
129
+ var EMPTY_RESPONSE = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/EmptyResponse");
130
+ function markEmptyResponse(error) {
131
+ Object.defineProperty(error, EMPTY_RESPONSE, { value: true });
132
+ return error;
133
+ }
134
+ function isEmptyResponse(value) {
135
+ return typeof value === "object" && value !== null && EMPTY_RESPONSE in value;
136
+ }
137
+ var TRANSPORT_FAILURE = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/TransportFailure");
138
+ function markTransportFailure(error) {
139
+ if (typeof error === "object" && error !== null) {
140
+ try {
141
+ Object.defineProperty(error, TRANSPORT_FAILURE, { value: true });
142
+ } catch {
143
+ }
144
+ }
145
+ return error;
146
+ }
147
+ function isTransportFailure(value) {
148
+ return typeof value === "object" && value !== null && TRANSPORT_FAILURE in value;
149
+ }
150
+ function describe(init) {
151
+ const status = init.status ?? 0;
152
+ const where = [init.method, init.url].filter(Boolean).join(" ");
153
+ const what = status > 0 ? `HTTP ${status}` : "request failed";
154
+ const head = where ? `${where} \u2192 ${what}` : what;
155
+ const code = init.code ? ` (${init.code})` : "";
156
+ return init.detail ? `${head}${code}: ${init.detail}` : `${head}${code}`;
157
+ }
158
+ var AdminError = class extends Error {
159
+ /** HTTP status. `0` means the request never produced one. */
160
+ status;
161
+ /** Machine-readable code from the response body, when it carried one. */
162
+ code;
163
+ /** Parsed response body, when there was one. */
164
+ body;
165
+ /** Requested URL, when known. */
166
+ url;
167
+ /** Request method, when known. */
168
+ method;
169
+ /**
170
+ * Text the failing side supplied — the only candidate for user-facing
171
+ * output. `undefined` when nothing was supplied.
172
+ */
173
+ detail;
174
+ /** The request completed but the answer was unusable. See `AdminErrorInit`. */
175
+ emptyResponse;
176
+ /** The request never reached the server. See `AdminErrorInit`. */
177
+ transportFailure;
178
+ constructor(init = {}) {
179
+ super(init.message ?? describe(init), { cause: init.cause });
180
+ this.name = "AdminError";
181
+ this.status = init.status ?? 0;
182
+ this.code = init.code;
183
+ this.body = init.body;
184
+ this.url = init.url;
185
+ this.method = init.method;
186
+ this.detail = init.detail;
187
+ this.emptyResponse = init.emptyResponse ?? false;
188
+ this.transportFailure = init.transportFailure ?? false;
189
+ Object.defineProperty(this, ADMIN_ERROR, { value: true });
190
+ markPlatformError(this);
191
+ }
192
+ };
193
+ function isAdminError(err) {
194
+ return typeof err === "object" && err !== null && ADMIN_ERROR in err;
195
+ }
196
+ function asRecord(value) {
197
+ return typeof value === "object" && value !== null ? value : void 0;
198
+ }
199
+ function asString(value) {
200
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
201
+ }
202
+ function isAxiosNoResponseError(value) {
203
+ const record = asRecord(value);
204
+ if (!record) return false;
205
+ return record.isAxiosError === true && record.request != null && record.response == null;
206
+ }
207
+ function readErrorDetail(body) {
208
+ const record = asRecord(body);
209
+ if (!record) return asString(body);
210
+ const message = record.message;
211
+ if (Array.isArray(message)) {
212
+ const parts = message.filter((part) => typeof part === "string");
213
+ return parts.length > 0 ? parts.join(", ") : void 0;
214
+ }
215
+ return asString(message) ?? asString(record.error) ?? asString(record.reason);
216
+ }
217
+ function readErrorCode(body) {
218
+ return asString(asRecord(body)?.code);
219
+ }
220
+ function toAdminError(err) {
221
+ if (isAdminError(err)) return err;
222
+ const record = asRecord(err);
223
+ const response = asRecord(record?.response);
224
+ if (response && typeof response.status === "number") {
225
+ const config = asRecord(record?.config);
226
+ const body = response.data;
227
+ return new AdminError({
228
+ status: response.status,
229
+ code: readErrorCode(body),
230
+ body,
231
+ url: asString(config?.url),
232
+ method: asString(config?.method)?.toUpperCase(),
233
+ // Only what the response actually said. `err.message` here is
234
+ // axios's own generated line ("Request failed with status code
235
+ // 401") — English, untranslated, and about the transport rather
236
+ // than about what went wrong. Letting it through as `detail` would
237
+ // make `adminErrorMessage` return it in preference to the
238
+ // localized wording, which is the whole point of that wording.
239
+ detail: readErrorDetail(body),
240
+ cause: err
241
+ });
242
+ }
243
+ if (record && typeof record.status === "number") {
244
+ const ours = isPlatformError(err);
245
+ return new AdminError({
246
+ status: record.status,
247
+ code: readErrorCode(record.body) ?? asString(record.code),
248
+ body: record.body,
249
+ // Same reasoning as above: `BundlesApiError`'s message is
250
+ // "Bundles API responded with HTTP 403", a diagnostic. It stays on
251
+ // `message`, where logs read it.
252
+ detail: readErrorDetail(record.body) ?? (ours ? void 0 : asString(record.message)),
253
+ // Asked of the throw site, not of the class: only the site that
254
+ // read the response knows whether one arrived. `status === 0 &&
255
+ // ours` was the same question put to the wrong witness — the
256
+ // loaders raise branded errors from a status the client chose, so
257
+ // a client that resolves a transport failure as `0` turned a
258
+ // failed GET into "the change may have been applied".
259
+ emptyResponse: isEmptyResponse(err),
260
+ transportFailure: isTransportFailure(err),
261
+ message: asString(record.message),
262
+ cause: err
263
+ });
264
+ }
265
+ if (err instanceof Error) {
266
+ const transport = isTransportFailure(err) || isAxiosNoResponseError(err);
267
+ const saidSomethingReadable = !transport && !(err instanceof TypeError);
268
+ return new AdminError({
269
+ detail: saidSomethingReadable ? asString(err.message) : void 0,
270
+ transportFailure: transport,
271
+ message: err.message,
272
+ cause: err
273
+ });
274
+ }
275
+ const text = asString(err) ?? asString(record?.message);
276
+ return new AdminError({
277
+ code: asString(record?.code),
278
+ detail: text,
279
+ message: text,
280
+ cause: err
281
+ });
282
+ }
283
+ function statusKey(status) {
284
+ if (status === 400 || status === 422) return "validation";
285
+ if (status === 401) return "unauthorized";
286
+ if (status === 403) return "forbidden";
287
+ if (status === 404) return "notFound";
288
+ if (status === 409) return "conflict";
289
+ if (status === 429) return "rateLimited";
290
+ if (status >= 500) return "server";
291
+ return void 0;
292
+ }
293
+ function adminErrorMessage(err, msgs) {
294
+ const error = toAdminError(err);
295
+ if (error.detail) return error.detail;
296
+ if (error.emptyResponse) return msgs.emptyResponse;
297
+ if (error.transportFailure) return msgs.network;
298
+ if (error.status > 0) {
299
+ const key = statusKey(error.status);
300
+ return key ? msgs[key] : formatMessage(msgs.httpStatus, { status: error.status });
301
+ }
302
+ return msgs.unexpected;
303
+ }
304
+
305
+ // src/client/http-json.ts
306
+ function serverAnswered(status) {
307
+ return status > 0;
308
+ }
309
+ function requireServerAnswer(status, method, url, raise) {
310
+ if (serverAnswered(status)) return;
311
+ throw markTransportFailure(
312
+ raise(`${method} ${url} produced no HTTP status \u2014 the request did not reach the server`)
313
+ );
314
+ }
315
+ var HttpJsonError = AdminError;
316
+ async function readErrorBody(res) {
317
+ try {
318
+ return await res.json();
319
+ } catch {
320
+ return void 0;
321
+ }
322
+ }
323
+ async function failed(res, method, url) {
324
+ const body = await readErrorBody(res);
325
+ return new AdminError({
326
+ status: res.status,
327
+ // Read here rather than left to the status: `status: 0` reaches
328
+ // `adminErrorMessage` as "nothing is known about this failure", and
329
+ // this seam knows better — it held the response.
330
+ transportFailure: !serverAnswered(res.status),
331
+ code: readErrorCode(body),
332
+ body,
333
+ url,
334
+ method,
335
+ // Shared with `toAdminError` on purpose: a NestJS `ValidationPipe`
336
+ // rejection carries `message` as an array, and reading it here in a
337
+ // second, narrower way is how those constraints went missing.
338
+ detail: readErrorDetail(body)
339
+ });
340
+ }
341
+ function trimTrailingSlashes(url) {
342
+ let end = url.length;
343
+ while (end > 0 && url[end - 1] === "/") end--;
344
+ return url.slice(0, end);
345
+ }
346
+ async function getJson(http, url) {
347
+ const res = await http(url);
348
+ if (res.status < 200 || res.status >= 300) {
349
+ throw await failed(res, "GET", url);
350
+ }
351
+ return await res.json();
352
+ }
353
+ async function postJson(http, url, body) {
354
+ const res = await http(url, {
355
+ method: "POST",
356
+ headers: { "Content-Type": "application/json" },
357
+ body: JSON.stringify(body)
358
+ });
359
+ if (res.status < 200 || res.status >= 300) {
360
+ throw await failed(res, "POST", url);
361
+ }
362
+ return await res.json();
363
+ }
364
+
365
+ // src/client/http/fetch-http-client.ts
366
+ var ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:|^\/\//i;
367
+ function resolveUrl(url, baseUrl) {
368
+ if (!baseUrl || ABSOLUTE_URL.test(url)) return url;
369
+ return `${trimTrailingSlashes(baseUrl)}${url.startsWith("/") ? "" : "/"}${url}`;
370
+ }
371
+ function createFetchHttpClient(options = {}) {
372
+ return async (url, init) => {
373
+ const headers = new Headers(await options.headers?.());
374
+ if (!headers.has("Accept")) headers.set("Accept", "application/json");
375
+ for (const [name, value] of Object.entries(init?.headers ?? {})) {
376
+ headers.set(name, value);
377
+ }
378
+ if (init?.body !== void 0 && !headers.has("Content-Type")) {
379
+ headers.set("Content-Type", "application/json");
380
+ }
381
+ let response;
382
+ try {
383
+ response = await fetch(resolveUrl(url, options.baseUrl), {
384
+ method: init?.method ?? "GET",
385
+ headers,
386
+ body: init?.body
387
+ });
388
+ } catch (err) {
389
+ throw markTransportFailure(err);
390
+ }
391
+ return response;
392
+ };
393
+ }
394
+
78
395
  // src/client/types.ts
79
396
  var NOOP_KV_STORE = {
80
397
  get: () => null,
@@ -116,51 +433,326 @@ function resolveLocalStorage() {
116
433
  }
117
434
  }
118
435
  function defaultHttpClient() {
119
- return (url, init) => fetch(url, init);
436
+ return createFetchHttpClient();
120
437
  }
121
438
 
122
- // src/client/http-json.ts
123
- var HttpJsonError = class extends Error {
124
- constructor(status, code) {
125
- super(code ?? `HTTP ${status}`);
126
- this.status = status;
127
- this.code = code;
128
- this.name = "HttpJsonError";
439
+ // src/client/http/axios-http-client.ts
440
+ function stripped(url, prefixes) {
441
+ for (const prefix of prefixes) {
442
+ const trimmed = trimTrailingSlashes(prefix);
443
+ if (!trimmed || !url.startsWith(trimmed)) continue;
444
+ const rest = url.slice(trimmed.length);
445
+ if (rest === "") return "/";
446
+ if (rest.startsWith("/")) return rest;
447
+ if (rest.startsWith("?") || rest.startsWith("#")) return `/${rest}`;
129
448
  }
130
- status;
131
- code;
132
- };
133
- async function extractCode(res) {
134
- try {
135
- const body = await res.json();
136
- return typeof body?.code === "string" ? body.code : void 0;
137
- } catch {
138
- return void 0;
449
+ return url;
450
+ }
451
+ function headerReader(headers) {
452
+ const record = typeof headers === "object" && headers !== null ? headers : {};
453
+ const lowered = /* @__PURE__ */ new Map();
454
+ for (const [key, value] of Object.entries(record)) lowered.set(key.toLowerCase(), value);
455
+ return (name) => {
456
+ const value = lowered.get(name.toLowerCase());
457
+ return value == null ? null : String(value);
458
+ };
459
+ }
460
+ function bodyIsRaw(response, declared) {
461
+ if (declared !== "auto") return declared === "raw";
462
+ if (response.data === "") return true;
463
+ const config = response.config;
464
+ if (config?.transformResponse === null) return true;
465
+ if (Array.isArray(config?.transformResponse) && config.transformResponse.length === 0) {
466
+ return true;
139
467
  }
468
+ const responseType = config?.responseType;
469
+ if (responseType === "json") return false;
470
+ if (typeof responseType === "string" && responseType !== "") return true;
471
+ const transitional = config?.transitional;
472
+ return typeof transitional === "object" && transitional !== null && transitional.forcedJSONParsing === false;
140
473
  }
141
- function trimTrailingSlashes(url) {
142
- let end = url.length;
143
- while (end > 0 && url[end - 1] === "/") end--;
144
- return url.slice(0, end);
474
+ async function readBody(data) {
475
+ if (typeof data === "string" || data === null || data === void 0) return data;
476
+ if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
477
+ if (ArrayBuffer.isView(data)) return new TextDecoder().decode(data);
478
+ const maybe = data;
479
+ if (typeof maybe.pipe === "function" || typeof maybe.getReader === "function") {
480
+ throw new TypeError(
481
+ "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."
482
+ );
483
+ }
484
+ if (typeof maybe.text === "function") return maybe.text.call(data);
485
+ return data;
145
486
  }
146
- async function getJson(http, url) {
147
- const res = await http(url);
148
- if (res.status < 200 || res.status >= 300) {
149
- throw new HttpJsonError(res.status, await extractCode(res));
487
+ function adapt(response, declared) {
488
+ return {
489
+ status: response.status,
490
+ headers: { get: headerReader(response.headers) },
491
+ json: async () => {
492
+ const data = await readBody(response.data);
493
+ if (typeof data !== "string") return data;
494
+ return bodyIsRaw({ ...response, data }, declared) ? JSON.parse(data) : data;
495
+ },
496
+ text: async () => {
497
+ const data = await readBody(response.data);
498
+ return typeof data === "string" ? data : JSON.stringify(data);
499
+ }
500
+ };
501
+ }
502
+ function createAxiosHttpClient(instance, options = {}) {
503
+ const prefixes = typeof options.stripPrefix === "string" ? [options.stripPrefix] : options.stripPrefix ?? [];
504
+ const responseBody = options.responseBody ?? "auto";
505
+ return async (url, init) => {
506
+ try {
507
+ return adapt(
508
+ await instance.request({
509
+ url: stripped(url, prefixes),
510
+ method: (init?.method ?? "GET").toUpperCase(),
511
+ headers: init?.headers,
512
+ data: init?.body
513
+ }),
514
+ responseBody
515
+ );
516
+ } catch (err) {
517
+ const response = err?.response;
518
+ if (response && typeof response.status === "number") {
519
+ return adapt(response, responseBody);
520
+ }
521
+ throw isAxiosNoResponseError(err) ? markTransportFailure(err) : err;
522
+ }
523
+ };
524
+ }
525
+
526
+ // src/client/resources/define-resource.ts
527
+ function defineResource(key, ops, options = {}) {
528
+ return { key, ops, projectScoped: options.projectScoped ?? false };
529
+ }
530
+ function requireProjectKey(def, ctx) {
531
+ if (!def.projectScoped) return;
532
+ if (typeof ctx.projectKey === "string" && ctx.projectKey.trim() !== "") return;
533
+ throw new Error(
534
+ `Resource "${def.key}" is project-scoped, but the context it is bound to names no project. Its requests would go out as \`?projectKey=\`, which the admin API answers for no project at all: the catalogue reads as empty and the first create fails validation instead. Name the project this admin administers \u2014 \`endpoints.projectKey\` for createSuperAdminApp(), \`context.projectKey\` for createResourceRegistry().`
535
+ );
536
+ }
537
+ function bindResource(def, http, ctx) {
538
+ const readContext = typeof ctx === "function" ? ctx : () => ctx;
539
+ if (def.projectScoped) requireProjectKey(def, readContext());
540
+ const bound = {};
541
+ for (const [name, op] of Object.entries(def.ops)) {
542
+ bound[name] = async (...args) => {
543
+ const context = readContext();
544
+ requireProjectKey(def, context);
545
+ return op(http, context, ...args);
546
+ };
150
547
  }
151
- return await res.json();
548
+ return bound;
152
549
  }
153
- async function postJson(http, url, body) {
154
- const res = await http(url, {
155
- method: "POST",
156
- headers: { "Content-Type": "application/json" },
157
- body: JSON.stringify(body)
550
+
551
+ // src/client/resources/resource-request.ts
552
+ async function requestJson(http, url, init = {}) {
553
+ const method = init.method ?? "GET";
554
+ const response = await http(url, {
555
+ method,
556
+ headers: { "content-type": "application/json", ...init.headers },
557
+ body: init.body === void 0 ? void 0 : JSON.stringify(init.body)
158
558
  });
159
- if (res.status < 200 || res.status >= 300) {
160
- throw new HttpJsonError(res.status, await extractCode(res));
559
+ requireServerAnswer(
560
+ response.status,
561
+ method,
562
+ url,
563
+ (diagnostic) => new AdminError({ status: response.status, url, method, message: diagnostic })
564
+ );
565
+ if (response.status === 204) return null;
566
+ const body = await response.json().catch(() => null);
567
+ if (response.status >= 400) {
568
+ throw new AdminError({
569
+ status: response.status,
570
+ code: readErrorCode(body),
571
+ body,
572
+ // The third place this had to be read, and the second time it was
573
+ // read a narrower way: without `detail` a page migrating onto these
574
+ // resources loses "Plan already exists" and shows the generic
575
+ // status sentence instead. One decision, one function.
576
+ detail: readErrorDetail(body),
577
+ url,
578
+ method
579
+ });
161
580
  }
162
- return await res.json();
581
+ return body;
582
+ }
583
+ async function requestJsonBody(http, url, what, init = {}) {
584
+ const body = await requestJson(http, url, init);
585
+ if (body === null || body === void 0) {
586
+ throw new AdminError({
587
+ status: 0,
588
+ url,
589
+ method: init.method ?? "GET",
590
+ // `what` is a diagnostic — "Create returned no body" — so it goes
591
+ // on `message`, not `detail`. As `detail` it out-ranked the
592
+ // localized wording and the operator read an English internal
593
+ // string instead of being told the change may already have landed.
594
+ message: what,
595
+ emptyResponse: true
596
+ });
597
+ }
598
+ return body;
599
+ }
600
+
601
+ // src/client/resources/list-resource.ts
602
+ var LIST_FIRST_PAGE = 1;
603
+ var LIST_PAGE_SIZE_DEFAULT = 50;
604
+ var LIST_PAGE_SIZE_MAX = 200;
605
+ var LIST_PAGINATION_PARAMS = ["page", "pageSize"];
606
+ function isSentInQuery(value) {
607
+ return value !== void 0 && value !== null && value !== "";
608
+ }
609
+ function appendFilter(params, filter) {
610
+ for (const [key, value] of Object.entries(filter ?? {})) {
611
+ if (!isSentInQuery(value)) continue;
612
+ params.set(key, String(value));
613
+ }
614
+ }
615
+ function filterQueryString(filter) {
616
+ const params = new URLSearchParams();
617
+ appendFilter(params, filter);
618
+ const query = params.toString();
619
+ return query ? `?${query}` : "";
620
+ }
621
+ function listUrl(endpoint, query = {}) {
622
+ const params = new URLSearchParams();
623
+ params.set("page", String(query.page ?? LIST_FIRST_PAGE));
624
+ params.set("pageSize", String(query.pageSize ?? LIST_PAGE_SIZE_DEFAULT));
625
+ appendFilter(params, query.filter);
626
+ return `${endpoint}${endpoint.includes("?") ? "&" : "?"}${params.toString()}`;
627
+ }
628
+ function readListPage(raw) {
629
+ if (Array.isArray(raw)) return { items: raw, total: raw.length };
630
+ if (raw === null || typeof raw !== "object") return { items: [], total: 0 };
631
+ const body = raw;
632
+ const page = {
633
+ items: Array.isArray(body.items) ? body.items : []
634
+ };
635
+ if (typeof body.total === "number") page.total = body.total;
636
+ if (typeof body.page === "number") page.page = body.page;
637
+ if (typeof body.pageSize === "number") page.pageSize = body.pageSize;
638
+ return page;
639
+ }
640
+ function clampListPage(page) {
641
+ return Math.max(LIST_FIRST_PAGE, Math.floor(page));
642
+ }
643
+ function clampListPageSize(size) {
644
+ return Math.min(LIST_PAGE_SIZE_MAX, Math.max(1, Math.floor(size)));
163
645
  }
646
+ function defineListOp(endpoint) {
647
+ return async (http, ctx, query) => readListPage(await requestJson(http, listUrl(endpoint(ctx), query)));
648
+ }
649
+
650
+ // src/client/resources/plans.resource.ts
651
+ function plansUrl(ctx) {
652
+ return `${ctx.apiBase}/catalog/plans`;
653
+ }
654
+ function versionsUrl(ctx) {
655
+ return `${ctx.apiBase}/catalog/plan-versions`;
656
+ }
657
+ function scoped(url, ctx) {
658
+ return `${url}?projectKey=${encodeURIComponent(ctx.projectKey)}`;
659
+ }
660
+ var plansResource = defineResource(
661
+ "plans",
662
+ {
663
+ list: async (http, ctx) => await requestJson(http, scoped(plansUrl(ctx), ctx)) ?? [],
664
+ tenantCounts: async (http, ctx) => await requestJson(
665
+ http,
666
+ scoped(`${plansUrl(ctx)}/tenant-counts`, ctx)
667
+ ) ?? {},
668
+ /**
669
+ * The project comes from the context, not from the caller.
670
+ *
671
+ * The endpoint takes it in the body, and the composable this replaces
672
+ * made that the caller's job — so a page had to keep hold of the
673
+ * project it was administering just to create in it, and `list()` and
674
+ * `create()` read two different sources for one value. A stale one
675
+ * creates a plan outside the project the list is showing.
676
+ */
677
+ create: (http, ctx, data) => requestJsonBody(http, plansUrl(ctx), "Create returned no body", {
678
+ method: "POST",
679
+ body: { ...data, projectKey: ctx.projectKey }
680
+ }),
681
+ update: (http, ctx, planId, data) => requestJsonBody(
682
+ http,
683
+ `${plansUrl(ctx)}/${planId}`,
684
+ "Update returned no body",
685
+ {
686
+ method: "PATCH",
687
+ body: data
688
+ }
689
+ ),
690
+ /** Marks the plan deleted. Fails while it still has versions. */
691
+ softDelete: async (http, ctx, planId) => {
692
+ await requestJson(http, `${plansUrl(ctx)}/${planId}`, { method: "DELETE" });
693
+ },
694
+ /** Removes the plan for good. 422 `PLAN_HAS_VERSIONS` when it cannot. */
695
+ hardDelete: async (http, ctx, planId) => {
696
+ await requestJson(http, `${plansUrl(ctx)}/${planId}/purge`, { method: "DELETE" });
697
+ }
698
+ },
699
+ { projectScoped: true }
700
+ );
701
+ var planVersionsResource = defineResource("planVersions", {
702
+ listForPlan: async (http, ctx, planId) => await requestJson(http, `${plansUrl(ctx)}/${planId}/versions`) ?? [],
703
+ createDraft: (http, ctx, planId, data) => requestJsonBody(
704
+ http,
705
+ `${plansUrl(ctx)}/${planId}/versions`,
706
+ "CreateDraft returned no body",
707
+ { method: "POST", body: data }
708
+ ),
709
+ updateDraft: (http, ctx, versionId, data) => requestJsonBody(
710
+ http,
711
+ `${versionsUrl(ctx)}/${versionId}`,
712
+ "UpdateDraft returned no body",
713
+ { method: "PATCH", body: data }
714
+ ),
715
+ publish: (http, ctx, versionId, options = {}) => requestJsonBody(
716
+ http,
717
+ `${versionsUrl(ctx)}/${versionId}/publish`,
718
+ "Publish returned no body",
719
+ { method: "POST", body: options }
720
+ ),
721
+ discardDraft: async (http, ctx, versionId) => {
722
+ await requestJson(http, `${versionsUrl(ctx)}/${versionId}`, { method: "DELETE" });
723
+ },
724
+ /**
725
+ * Ends a live version on `endsAt`, with no successor. Idempotent — a
726
+ * second call with another date overwrites the first.
727
+ *
728
+ * `endsAt` is the date itself, not a payload: the endpoint's body is
729
+ * `{ endsAt }` and wrapping it is this operation's job, not the caller's.
730
+ */
731
+ terminate: (http, ctx, versionId, endsAt) => requestJsonBody(
732
+ http,
733
+ `${versionsUrl(ctx)}/${versionId}/terminate`,
734
+ "Terminate returned no body",
735
+ { method: "POST", body: { endsAt } }
736
+ )
737
+ });
738
+
739
+ // src/client/resources/tenants.resource.ts
740
+ var tenantsResource = defineResource("tenants", {
741
+ list: defineListOp((ctx) => `${ctx.apiBase}/tenants`)
742
+ });
743
+
744
+ // src/client/resources/audit.resource.ts
745
+ var auditResource = defineResource("audit", {
746
+ list: defineListOp((ctx) => `${ctx.apiBase}/audit`)
747
+ });
748
+
749
+ // src/client/resources/index.ts
750
+ var platformResources = {
751
+ plans: plansResource,
752
+ planVersions: planVersionsResource,
753
+ tenants: tenantsResource,
754
+ audit: auditResource
755
+ };
164
756
 
165
757
  // src/client/boot-loader.ts
166
758
  var BootLoadError = class extends Error {
@@ -168,6 +760,7 @@ var BootLoadError = class extends Error {
168
760
  super(message);
169
761
  this.status = status;
170
762
  this.name = "BootLoadError";
763
+ markPlatformError(this);
171
764
  }
172
765
  status;
173
766
  };
@@ -185,19 +778,27 @@ var BootLoader = class {
185
778
  }
186
779
  async load() {
187
780
  const res = await this.http(this.endpoint);
781
+ requireServerAnswer(
782
+ res.status,
783
+ "GET",
784
+ this.endpoint,
785
+ (diagnostic) => new BootLoadError(res.status, diagnostic)
786
+ );
188
787
  if (res.status !== 200) {
189
- throw new BootLoadError(res.status, `Boot-Endpunkt antwortete HTTP ${res.status}`);
788
+ throw new BootLoadError(res.status, `Boot endpoint responded with HTTP ${res.status}`);
190
789
  }
191
790
  return await res.json();
192
791
  }
193
792
  };
194
793
 
195
794
  // src/client/manifest-loader.ts
795
+ var NOT_MODIFIED = 304;
196
796
  var ManifestLoadError = class extends Error {
197
797
  constructor(status, message) {
198
798
  super(message);
199
799
  this.status = status;
200
800
  this.name = "ManifestLoadError";
801
+ markPlatformError(this);
201
802
  }
202
803
  status;
203
804
  };
@@ -225,24 +826,48 @@ var ManifestLoader = class {
225
826
  /**
226
827
  * Loads the current manifest. On a cache hit (304) the cached
227
828
  * body is returned — otherwise the fresh server body.
829
+ *
830
+ * A 304 with no usable cached body is not a failure to report but one to
831
+ * undo: the request only carried `If-None-Match` because this loader put
832
+ * the ETag there, and the cache it validated against is this loader's own.
833
+ * So it drops the pair and asks once more without the header, which is the
834
+ * same recovery a person would have had to trigger by hand — except that
835
+ * the person reading an admin screen has neither this loader nor a
836
+ * console. Exactly one retry: a 304 to a request that asked nothing
837
+ * conditional is a server fault, and no repetition fixes it.
228
838
  */
229
839
  async load() {
230
840
  const cachedEtag = this.storage.get(this.etagKey);
841
+ const res = await this.request(cachedEtag);
842
+ if (res.status !== NOT_MODIFIED) return this.acceptFresh(res);
843
+ const cached = this.readCachedBody();
844
+ if (cached) return cached.body;
845
+ if (cachedEtag) {
846
+ this.clearCache();
847
+ const fresh = await this.request(null);
848
+ if (fresh.status !== NOT_MODIFIED) return this.acceptFresh(fresh);
849
+ }
850
+ throw new ManifestLoadError(
851
+ NOT_MODIFIED,
852
+ "Manifest endpoint answered HTTP 304 to a request without If-None-Match"
853
+ );
854
+ }
855
+ /** Sends the manifest GET, conditional when an ETag is supplied. */
856
+ request(etag) {
231
857
  const headers = {};
232
858
  const token = this.getAuthToken?.();
233
859
  if (token) headers.Authorization = `Bearer ${token}`;
234
- if (cachedEtag) headers["If-None-Match"] = cachedEtag;
235
- const res = await this.http(this.endpoint, { method: "GET", headers });
236
- if (res.status === 304) {
237
- const cachedBody = this.readCachedBody();
238
- if (!cachedBody) {
239
- throw new ManifestLoadError(
240
- 304,
241
- "Server returned 304 but the cache body is missing \u2014 call clearCache() and reload"
242
- );
243
- }
244
- return cachedBody.body;
245
- }
860
+ if (etag) headers["If-None-Match"] = etag;
861
+ return this.http(this.endpoint, { method: "GET", headers });
862
+ }
863
+ /** Takes a non-304 response as the current manifest and caches it. */
864
+ async acceptFresh(res) {
865
+ requireServerAnswer(
866
+ res.status,
867
+ "GET",
868
+ this.endpoint,
869
+ (diagnostic) => new ManifestLoadError(res.status, diagnostic)
870
+ );
246
871
  if (res.status !== 200) {
247
872
  throw new ManifestLoadError(
248
873
  res.status,
@@ -577,7 +1202,7 @@ var ActionRegistry = class {
577
1202
  // src/client/batch-column-fetcher.ts
578
1203
  var BatchColumnDriftError = class extends Error {
579
1204
  constructor(column, reason) {
580
- super(`Spalte "${column.key}": ${reason}`);
1205
+ super(`Column "${column.key}": ${reason}`);
581
1206
  this.column = column;
582
1207
  this.name = "BatchColumnDriftError";
583
1208
  }
@@ -620,7 +1245,7 @@ var BatchColumnFetcher = class {
620
1245
  const res = await this.http(url, { method: "GET", headers });
621
1246
  if (res.status !== 200) {
622
1247
  throw new Error(
623
- `Spalte "${column.key}" \u2014 Endpoint ${column.endpoint} antwortete HTTP ${res.status}`
1248
+ `Column "${column.key}" \u2014 endpoint ${column.endpoint} responded with HTTP ${res.status}`
624
1249
  );
625
1250
  }
626
1251
  const body = await res.json();
@@ -696,10 +1321,10 @@ function createAdminResourceClient(options) {
696
1321
  tenantsEndpoint,
697
1322
  loadTenantDetail: (slug) => getJson2(`${tenantsEndpoint}/${encodeURIComponent(slug)}`),
698
1323
  loadUsers: (filter) => getJson2(
699
- `${base}/users${queryString({ q: filter.q, tenant: filter.tenant })}`
1324
+ `${base}/users${filterQueryString({ q: filter.q, tenant: filter.tenant })}`
700
1325
  ),
701
1326
  loadAudit: (filter) => getJson2(
702
- `${base}/audit${queryString({
1327
+ `${base}/audit${filterQueryString({
703
1328
  actor: filter.actor,
704
1329
  action: filter.action,
705
1330
  entity: filter.entity,
@@ -709,7 +1334,7 @@ function createAdminResourceClient(options) {
709
1334
  ),
710
1335
  loadSubscriptions: () => getJson2(`${base}/subscriptions`),
711
1336
  loadPromos: (filter) => getJson2(
712
- `${base}/promo-codes${queryString({
1337
+ `${base}/promo-codes${filterQueryString({
713
1338
  search: filter.search,
714
1339
  status: filter.status
715
1340
  })}`
@@ -734,21 +1359,6 @@ function assertSuccess(status, method, url) {
734
1359
  throw new Error(`${method} ${url} \u2192 HTTP ${status}`);
735
1360
  }
736
1361
  }
737
- function queryString(params) {
738
- const search = new URLSearchParams();
739
- for (const [key, value2] of Object.entries(params)) {
740
- if (value2 === void 0 || value2 === null || value2 === "") continue;
741
- search.set(key, String(value2));
742
- }
743
- const value = search.toString();
744
- return value ? `?${value}` : "";
745
- }
746
-
747
- // src/client/i18n/format.ts
748
- var import_types4 = require("@saasicat/types");
749
- function formatMessage(template, params) {
750
- return (0, import_types4.formatErrorMessage)(template, params);
751
- }
752
1362
 
753
1363
  // src/client/i18n/currency.ts
754
1364
  var DEFAULT_CURRENCY = "EUR";
@@ -1447,9 +2057,6 @@ var discoveryMessages = defineMessages(
1447
2057
  replacedBy: "ersetzt durch {key}",
1448
2058
  replaces: "ersetzt: {keys}",
1449
2059
  reapproveEmphasis: "erneut freigeben",
1450
- errorDiscoveryHttp: "Discovery-Endpoint antwortete mit HTTP {status}",
1451
- errorRescanHttp: "Discovery-Rescan antwortete mit HTTP {status}",
1452
- errorCatalogHttp: "Catalog-Entries-API antwortete mit HTTP {status}",
1453
2060
  capList: {
1454
2061
  orphanFeature: "Feature im Katalog ohne implementierende Capability \u2014 blockiert im blocking-Strict-Mode das Plan-Publish.",
1455
2062
  removedFromCode: "aus Code entfernt"
@@ -1551,9 +2158,6 @@ var discoveryMessages = defineMessages(
1551
2158
  replacedBy: "replaced by {key}",
1552
2159
  replaces: "replaces: {keys}",
1553
2160
  reapproveEmphasis: "approve again",
1554
- errorDiscoveryHttp: "Discovery endpoint responded with HTTP {status}",
1555
- errorRescanHttp: "Discovery rescan responded with HTTP {status}",
1556
- errorCatalogHttp: "Catalog entries API responded with HTTP {status}",
1557
2161
  capList: {
1558
2162
  orphanFeature: "Feature in the catalog without an implementing capability \u2014 blocks plan publishing in blocking strict mode.",
1559
2163
  removedFromCode: "removed from code"
@@ -1758,6 +2362,36 @@ var emailMessages = defineMessages(
1758
2362
  }
1759
2363
  );
1760
2364
 
2365
+ // src/client/i18n/messages/errors.ts
2366
+ var errorsMessages = defineMessages(
2367
+ {
2368
+ network: "Die Anfrage konnte nicht abgeschlossen werden. Bitte Verbindung pr\xFCfen.",
2369
+ emptyResponse: "Der Server hat nichts zur\xFCckgegeben. Bitte pr\xFCfen, ob die \xC4nderung angekommen ist.",
2370
+ unauthorized: "Die Sitzung ist abgelaufen. Bitte erneut anmelden.",
2371
+ forbidden: "F\xFCr diese Aktion fehlt die Berechtigung.",
2372
+ notFound: "Der angeforderte Eintrag existiert nicht mehr.",
2373
+ conflict: "Der Eintrag wurde zwischenzeitlich ge\xE4ndert. Bitte neu laden.",
2374
+ validation: "Die Eingaben wurden nicht akzeptiert.",
2375
+ rateLimited: "Zu viele Anfragen. Bitte kurz warten und erneut versuchen.",
2376
+ server: "Der Server konnte die Anfrage nicht verarbeiten.",
2377
+ httpStatus: "Die Anfrage ist fehlgeschlagen (HTTP {status}).",
2378
+ unexpected: "Es ist ein unerwarteter Fehler aufgetreten. Bitte erneut versuchen."
2379
+ },
2380
+ {
2381
+ network: "The request could not be completed. Please check your connection.",
2382
+ emptyResponse: "The server returned nothing. Please check whether the change was applied.",
2383
+ unauthorized: "Your session has expired. Please sign in again.",
2384
+ forbidden: "You do not have permission for this action.",
2385
+ notFound: "The requested entry no longer exists.",
2386
+ conflict: "The entry changed in the meantime. Please reload.",
2387
+ validation: "The input was not accepted.",
2388
+ rateLimited: "Too many requests. Please wait a moment and try again.",
2389
+ server: "The server could not process the request.",
2390
+ httpStatus: "The request failed (HTTP {status}).",
2391
+ unexpected: "Something went wrong. Please try again."
2392
+ }
2393
+ );
2394
+
1761
2395
  // src/client/i18n/messages/marketing.ts
1762
2396
  var marketingMessages = defineMessages(
1763
2397
  {
@@ -1882,9 +2516,6 @@ var marketingMessages = defineMessages(
1882
2516
  colorLabel: "Farbe",
1883
2517
  delete: "Aktion l\xF6schen",
1884
2518
  deleteConfirm: "Aktion \u201E{label}\u201C wirklich l\xF6schen?"
1885
- },
1886
- errors: {
1887
- projectionsApi: "MarketingProjections-API antwortete mit HTTP {status}"
1888
2519
  }
1889
2520
  },
1890
2521
  {
@@ -2009,9 +2640,6 @@ var marketingMessages = defineMessages(
2009
2640
  colorLabel: "Color",
2010
2641
  delete: "Delete promotion",
2011
2642
  deleteConfirm: 'Really delete promotion "{label}"?'
2012
- },
2013
- errors: {
2014
- projectionsApi: "MarketingProjections API responded with HTTP {status}"
2015
2643
  }
2016
2644
  }
2017
2645
  );
@@ -3205,7 +3833,6 @@ var promosMessages = defineMessages(
3205
3833
  {
3206
3834
  title: "Promo-Codes",
3207
3835
  createAction: "Promo-Code anlegen",
3208
- apiErrorHttpStatus: "Promotions-API antwortete mit HTTP {status}",
3209
3836
  list: {
3210
3837
  searchLabel: "Suche (Code)",
3211
3838
  tileScheduled: "Geplant",
@@ -3303,7 +3930,6 @@ var promosMessages = defineMessages(
3303
3930
  {
3304
3931
  title: "Promo codes",
3305
3932
  createAction: "Create promo code",
3306
- apiErrorHttpStatus: "Promotions API responded with HTTP {status}",
3307
3933
  list: {
3308
3934
  searchLabel: "Search (code)",
3309
3935
  tileScheduled: "Scheduled",
@@ -3411,7 +4037,13 @@ var shellMessages = defineMessages(
3411
4037
  subtitle: "SuperAdmin \xB7 Plattform-Verwaltung",
3412
4038
  roleBadge: "SUPER ADMIN",
3413
4039
  logout: "Abmelden",
3414
- language: "Sprache"
4040
+ language: "Sprache",
4041
+ theme: {
4042
+ label: "Darstellung",
4043
+ light: "Hell",
4044
+ dark: "Dunkel",
4045
+ system: "System"
4046
+ }
3415
4047
  },
3416
4048
  drawer: {
3417
4049
  docs: "Doku \xF6ffnen"
@@ -3480,7 +4112,13 @@ var shellMessages = defineMessages(
3480
4112
  subtitle: "SuperAdmin \xB7 Platform administration",
3481
4113
  roleBadge: "SUPER ADMIN",
3482
4114
  logout: "Sign out",
3483
- language: "Language"
4115
+ language: "Language",
4116
+ theme: {
4117
+ label: "Appearance",
4118
+ light: "Light",
4119
+ dark: "Dark",
4120
+ system: "System"
4121
+ }
3484
4122
  },
3485
4123
  drawer: {
3486
4124
  docs: "Open documentation"
@@ -3724,6 +4362,7 @@ var usersMessages = defineMessages(
3724
4362
  function catalogFor(locale) {
3725
4363
  return {
3726
4364
  common: commonMessages[locale],
4365
+ errors: errorsMessages[locale],
3727
4366
  nav: navMessages[locale],
3728
4367
  shell: shellMessages[locale],
3729
4368
  dashboard: dashboardMessages[locale],
@@ -3912,6 +4551,7 @@ function identityChipStyle(accent) {
3912
4551
  ADMIN_UI_VERSION,
3913
4552
  ActionDefNotInManifestError,
3914
4553
  ActionRegistry,
4554
+ AdminError,
3915
4555
  BatchColumnDriftError,
3916
4556
  BatchColumnFetcher,
3917
4557
  BootLoadError,
@@ -3923,6 +4563,10 @@ function identityChipStyle(accent) {
3923
4563
  IDENTITY_ACCENT_VALUES,
3924
4564
  IDENTITY_NEUTRAL,
3925
4565
  IDENTITY_NEUTRAL_VALUE,
4566
+ LIST_FIRST_PAGE,
4567
+ LIST_PAGE_SIZE_DEFAULT,
4568
+ LIST_PAGE_SIZE_MAX,
4569
+ LIST_PAGINATION_PARAMS,
3926
4570
  ManifestLoadError,
3927
4571
  ManifestLoader,
3928
4572
  MissingHandlerError,
@@ -3930,32 +4574,63 @@ function identityChipStyle(accent) {
3930
4574
  SA_LOCALES,
3931
4575
  SA_LOCALE_LABELS,
3932
4576
  SA_MESSAGES,
4577
+ adminErrorMessage,
4578
+ auditResource,
4579
+ bindResource,
3933
4580
  buildRoutes,
3934
4581
  buildSidebar,
4582
+ clampListPage,
4583
+ clampListPageSize,
3935
4584
  countPlans,
3936
4585
  createAdminResourceClient,
4586
+ createAxiosHttpClient,
4587
+ createFetchHttpClient,
3937
4588
  createSaCatalog,
3938
4589
  defaultHttpClient,
3939
4590
  defaultKvStore,
3940
4591
  defaultSectionOrder,
4592
+ defineListOp,
3941
4593
  defineMessages,
4594
+ defineResource,
4595
+ filterQueryString,
3942
4596
  formatCurrency,
3943
4597
  formatMessage,
3944
4598
  getJson,
3945
4599
  identityAccentAt,
3946
4600
  identityAccentFor,
3947
4601
  identityChipStyle,
4602
+ isAdminError,
4603
+ isAxiosNoResponseError,
3948
4604
  isCurrentlyValid,
4605
+ isEmptyResponse,
3949
4606
  isExpired,
3950
4607
  isFutureScheduled,
4608
+ isPlatformError,
3951
4609
  isProductionBoot,
3952
4610
  isSaBuiltinLocale,
4611
+ isSentInQuery,
4612
+ isTransportFailure,
4613
+ listUrl,
4614
+ markEmptyResponse,
4615
+ markPlatformError,
4616
+ markTransportFailure,
3953
4617
  mergeMessages,
4618
+ planVersionsResource,
4619
+ plansResource,
4620
+ platformResources,
3954
4621
  postJson,
4622
+ readErrorCode,
4623
+ readErrorDetail,
4624
+ readListPage,
4625
+ requestJson,
4626
+ requestJsonBody,
4627
+ requireServerAnswer,
3955
4628
  resolveExtension,
3956
4629
  resolveLoginBranding,
3957
4630
  resolveMessages,
3958
4631
  resolvePlans,
4632
+ tenantsResource,
4633
+ toAdminError,
3959
4634
  todayIsoDate,
3960
4635
  trimTrailingSlashes
3961
4636
  });