@saasicat/ui-vue 0.25.0 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +55 -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-COVQLDBZ.js} +759 -24
  5. package/dist/{chunk-TQQMKWN6.js → chunk-DT25QAU2.js} +119 -7
  6. package/dist/{chunk-D74FFCCP.js → chunk-O3Y3BYYS.js} +96 -178
  7. package/dist/client/index.cjs +770 -84
  8. package/dist/client/index.d.cts +429 -9
  9. package/dist/client/index.d.ts +429 -9
  10. package/dist/client/index.js +84 -12
  11. package/dist/index.cjs +1337 -295
  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 +585 -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 +6 -4
  22. package/src/client/admin-error.ts +569 -0
  23. package/src/client/admin-resource-client.ts +9 -12
  24. package/src/client/attach-cause.ts +21 -0
  25. package/src/client/batch-column-fetcher.ts +2 -2
  26. package/src/client/boot-loader.ts +12 -1
  27. package/src/client/http/axios-http-client.ts +383 -0
  28. package/src/client/http/fetch-http-client.ts +89 -0
  29. package/src/client/http/index.ts +6 -0
  30. package/src/client/http-json.ts +82 -16
  31. package/src/client/i18n/messages/discovery.ts +0 -6
  32. package/src/client/i18n/messages/errors.ts +44 -0
  33. package/src/client/i18n/messages/marketing.ts +0 -6
  34. package/src/client/i18n/messages/promos.ts +0 -2
  35. package/src/client/i18n/messages/shell.ts +12 -0
  36. package/src/client/i18n/messages.ts +3 -0
  37. package/src/client/index.ts +6 -0
  38. package/src/client/manifest-loader.ts +65 -15
  39. package/src/client/nav-builder.ts +3 -1
  40. package/src/client/resources/audit.resource.ts +25 -0
  41. package/src/client/resources/define-resource.ts +166 -0
  42. package/src/client/resources/index.ts +42 -0
  43. package/src/client/resources/list-resource.ts +176 -0
  44. package/src/client/resources/plans.resource.ts +169 -0
  45. package/src/client/resources/resource-request.ts +117 -0
  46. package/src/client/resources/tenants.resource.ts +27 -0
  47. package/src/client/types.ts +10 -3
  48. package/src/components/BundleVersionPublishDialog.vue +1 -1
  49. package/src/components/MarketingPromotionsTab.vue +263 -241
  50. package/src/components/ThemeSwitcher.vue +85 -0
  51. package/src/components/bundle-editor/BundleCreatePanel.vue +1 -1
  52. package/src/components/dialogs/PromoCodeDialogFields.vue +20 -20
  53. package/src/components/plan/PlanCycleToggle.vue +1 -1
  54. package/src/components/plan-detail/PlanVersionsPanel.vue +5 -2
  55. package/src/index.ts +5 -0
  56. package/src/pages-standard/AdminLayout.vue +57 -0
  57. package/src/pages-standard/AdminManifestErrorPage.vue +4 -3
  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 +44 -7
  62. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +7 -2
  63. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +44 -6
  64. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +8 -2
  65. package/src/pages-tenant/PackageSnapshotPanel.vue +31 -2
  66. package/src/quasar/confirm.ts +83 -0
  67. package/src/quasar/create-super-admin-app.ts +84 -10
  68. package/src/quasar/dark-bridge.ts +47 -10
  69. package/src/quasar/index.ts +1 -0
  70. package/src/vue/create-admin-routes.ts +9 -1
  71. package/src/vue/platform-loaders.ts +12 -2
  72. package/src/vue/resource-registry.ts +211 -0
  73. package/src/vue/super-admin-context.ts +20 -5
  74. package/src/vue/ui-confirm.ts +76 -0
  75. package/src/vue/use-api-list.ts +50 -45
  76. package/src/vue/use-async-action.ts +170 -0
  77. package/src/vue/use-async-data.ts +83 -0
  78. package/src/vue/use-bundles.ts +34 -7
  79. package/src/vue/use-catalog-entries.ts +37 -12
  80. package/src/vue/use-discovery.ts +27 -6
  81. package/src/vue/use-marketing-projections.ts +29 -7
  82. package/src/vue/use-plans.ts +36 -8
  83. package/src/vue/use-promotions.ts +27 -7
  84. package/src/vue/use-resource-list.ts +272 -0
  85. package/src/vue/use-sa-theme.ts +34 -5
  86. 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,298 @@ 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/attach-cause.ts
115
+ function attachCause(error, cause) {
116
+ Object.defineProperty(error, "cause", {
117
+ value: cause,
118
+ configurable: true,
119
+ writable: true
120
+ });
121
+ return error;
122
+ }
123
+
124
+ // src/client/i18n/format.ts
125
+ var import_types = require("@saasicat/types");
126
+ function formatMessage(template, params) {
127
+ return (0, import_types.formatErrorMessage)(template, params);
128
+ }
129
+
130
+ // src/client/admin-error.ts
131
+ var ADMIN_ERROR = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/AdminError");
132
+ var PLATFORM_ERROR = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/PlatformError");
133
+ function markPlatformError(error) {
134
+ Object.defineProperty(error, PLATFORM_ERROR, { value: true });
135
+ }
136
+ function isPlatformError(value) {
137
+ return typeof value === "object" && value !== null && PLATFORM_ERROR in value;
138
+ }
139
+ var EMPTY_RESPONSE = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/EmptyResponse");
140
+ function markEmptyResponse(error) {
141
+ Object.defineProperty(error, EMPTY_RESPONSE, { value: true });
142
+ return error;
143
+ }
144
+ function isEmptyResponse(value) {
145
+ return typeof value === "object" && value !== null && EMPTY_RESPONSE in value;
146
+ }
147
+ var TRANSPORT_FAILURE = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/TransportFailure");
148
+ function markTransportFailure(error) {
149
+ if (typeof error === "object" && error !== null) {
150
+ try {
151
+ Object.defineProperty(error, TRANSPORT_FAILURE, { value: true });
152
+ } catch {
153
+ }
154
+ }
155
+ return error;
156
+ }
157
+ function isTransportFailure(value) {
158
+ return typeof value === "object" && value !== null && TRANSPORT_FAILURE in value;
159
+ }
160
+ function describe(init) {
161
+ const status = init.status ?? 0;
162
+ const where = [init.method, init.url].filter(Boolean).join(" ");
163
+ const what = status > 0 ? `HTTP ${status}` : "request failed";
164
+ const head = where ? `${where} \u2192 ${what}` : what;
165
+ const code = init.code ? ` (${init.code})` : "";
166
+ return init.detail ? `${head}${code}: ${init.detail}` : `${head}${code}`;
167
+ }
168
+ var AdminError = class extends Error {
169
+ /** HTTP status. `0` means the request never produced one. */
170
+ status;
171
+ /** Machine-readable code from the response body, when it carried one. */
172
+ code;
173
+ /** Parsed response body, when there was one. */
174
+ body;
175
+ /** Requested URL, when known. */
176
+ url;
177
+ /** Request method, when known. */
178
+ method;
179
+ /**
180
+ * Text the failing side supplied — the only candidate for user-facing
181
+ * output. `undefined` when nothing was supplied.
182
+ */
183
+ detail;
184
+ /** The request completed but the answer was unusable. See `AdminErrorInit`. */
185
+ emptyResponse;
186
+ /** The request never reached the server. See `AdminErrorInit`. */
187
+ transportFailure;
188
+ constructor(init = {}) {
189
+ super(init.message ?? describe(init));
190
+ if (init.cause !== void 0) attachCause(this, init.cause);
191
+ this.name = "AdminError";
192
+ this.status = init.status ?? 0;
193
+ this.code = init.code;
194
+ this.body = init.body;
195
+ this.url = init.url;
196
+ this.method = init.method;
197
+ this.detail = init.detail;
198
+ this.emptyResponse = init.emptyResponse ?? false;
199
+ this.transportFailure = init.transportFailure ?? false;
200
+ Object.defineProperty(this, ADMIN_ERROR, { value: true });
201
+ markPlatformError(this);
202
+ }
203
+ };
204
+ function isAdminError(err) {
205
+ return typeof err === "object" && err !== null && ADMIN_ERROR in err;
206
+ }
207
+ function asRecord(value) {
208
+ return typeof value === "object" && value !== null ? value : void 0;
209
+ }
210
+ function asString(value) {
211
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
212
+ }
213
+ function isAxiosNoResponseError(value) {
214
+ const record = asRecord(value);
215
+ if (!record) return false;
216
+ return record.isAxiosError === true && record.request != null && record.response == null;
217
+ }
218
+ function readErrorDetail(body) {
219
+ const record = asRecord(body);
220
+ if (!record) return asString(body);
221
+ const message = record.message;
222
+ if (Array.isArray(message)) {
223
+ const parts = message.filter((part) => typeof part === "string");
224
+ return parts.length > 0 ? parts.join(", ") : void 0;
225
+ }
226
+ return asString(message) ?? asString(record.error) ?? asString(record.reason);
227
+ }
228
+ function readErrorCode(body) {
229
+ return asString(asRecord(body)?.code);
230
+ }
231
+ function toAdminError(err) {
232
+ if (isAdminError(err)) return err;
233
+ const record = asRecord(err);
234
+ const response = asRecord(record?.response);
235
+ if (response && typeof response.status === "number") {
236
+ const config = asRecord(record?.config);
237
+ const body = response.data;
238
+ return new AdminError({
239
+ status: response.status,
240
+ code: readErrorCode(body),
241
+ body,
242
+ url: asString(config?.url),
243
+ method: asString(config?.method)?.toUpperCase(),
244
+ // Only what the response actually said. `err.message` here is
245
+ // axios's own generated line ("Request failed with status code
246
+ // 401") — English, untranslated, and about the transport rather
247
+ // than about what went wrong. Letting it through as `detail` would
248
+ // make `adminErrorMessage` return it in preference to the
249
+ // localized wording, which is the whole point of that wording.
250
+ detail: readErrorDetail(body),
251
+ cause: err
252
+ });
253
+ }
254
+ if (record && typeof record.status === "number") {
255
+ const ours = isPlatformError(err);
256
+ return new AdminError({
257
+ status: record.status,
258
+ code: readErrorCode(record.body) ?? asString(record.code),
259
+ body: record.body,
260
+ // Same reasoning as above: `BundlesApiError`'s message is
261
+ // "Bundles API responded with HTTP 403", a diagnostic. It stays on
262
+ // `message`, where logs read it.
263
+ detail: readErrorDetail(record.body) ?? (ours ? void 0 : asString(record.message)),
264
+ // Asked of the throw site, not of the class: only the site that
265
+ // read the response knows whether one arrived. `status === 0 &&
266
+ // ours` was the same question put to the wrong witness — the
267
+ // loaders raise branded errors from a status the client chose, so
268
+ // a client that resolves a transport failure as `0` turned a
269
+ // failed GET into "the change may have been applied".
270
+ emptyResponse: isEmptyResponse(err),
271
+ transportFailure: isTransportFailure(err),
272
+ message: asString(record.message),
273
+ cause: err
274
+ });
275
+ }
276
+ if (err instanceof Error) {
277
+ const transport = isTransportFailure(err) || isAxiosNoResponseError(err);
278
+ const saidSomethingReadable = !transport && !(err instanceof TypeError);
279
+ return new AdminError({
280
+ detail: saidSomethingReadable ? asString(err.message) : void 0,
281
+ transportFailure: transport,
282
+ message: err.message,
283
+ cause: err
284
+ });
285
+ }
286
+ const text = asString(err) ?? asString(record?.message);
287
+ return new AdminError({
288
+ code: asString(record?.code),
289
+ detail: text,
290
+ message: text,
291
+ cause: err
292
+ });
293
+ }
294
+ function statusKey(status) {
295
+ if (status === 400 || status === 422) return "validation";
296
+ if (status === 401) return "unauthorized";
297
+ if (status === 403) return "forbidden";
298
+ if (status === 404) return "notFound";
299
+ if (status === 409) return "conflict";
300
+ if (status === 429) return "rateLimited";
301
+ if (status >= 500) return "server";
302
+ return void 0;
303
+ }
304
+ function adminErrorMessage(err, msgs) {
305
+ const error = toAdminError(err);
306
+ if (error.detail) return error.detail;
307
+ if (error.emptyResponse) return msgs.emptyResponse;
308
+ if (error.transportFailure) return msgs.network;
309
+ if (error.status > 0) {
310
+ const key = statusKey(error.status);
311
+ return key ? msgs[key] : formatMessage(msgs.httpStatus, { status: error.status });
312
+ }
313
+ return msgs.unexpected;
314
+ }
315
+
316
+ // src/client/http-json.ts
317
+ function serverAnswered(status) {
318
+ return status > 0;
319
+ }
320
+ function requireServerAnswer(status, method, url, raise) {
321
+ if (serverAnswered(status)) return;
322
+ throw markTransportFailure(
323
+ raise(`${method} ${url} produced no HTTP status \u2014 the request did not reach the server`)
324
+ );
325
+ }
326
+ var HttpJsonError = AdminError;
327
+ async function readErrorBody(res) {
328
+ try {
329
+ return await res.json();
330
+ } catch {
331
+ return void 0;
332
+ }
333
+ }
334
+ async function failed(res, method, url) {
335
+ const body = await readErrorBody(res);
336
+ return new AdminError({
337
+ status: res.status,
338
+ // Read here rather than left to the status: `status: 0` reaches
339
+ // `adminErrorMessage` as "nothing is known about this failure", and
340
+ // this seam knows better — it held the response.
341
+ transportFailure: !serverAnswered(res.status),
342
+ code: readErrorCode(body),
343
+ body,
344
+ url,
345
+ method,
346
+ // Shared with `toAdminError` on purpose: a NestJS `ValidationPipe`
347
+ // rejection carries `message` as an array, and reading it here in a
348
+ // second, narrower way is how those constraints went missing.
349
+ detail: readErrorDetail(body)
350
+ });
351
+ }
352
+ function trimTrailingSlashes(url) {
353
+ let end = url.length;
354
+ while (end > 0 && url[end - 1] === "/") end--;
355
+ return url.slice(0, end);
356
+ }
357
+ async function getJson(http, url) {
358
+ const res = await http(url);
359
+ if (res.status < 200 || res.status >= 300) {
360
+ throw await failed(res, "GET", url);
361
+ }
362
+ return await res.json();
363
+ }
364
+ async function postJson(http, url, body) {
365
+ const res = await http(url, {
366
+ method: "POST",
367
+ headers: { "Content-Type": "application/json" },
368
+ body: JSON.stringify(body)
369
+ });
370
+ if (res.status < 200 || res.status >= 300) {
371
+ throw await failed(res, "POST", url);
372
+ }
373
+ return await res.json();
374
+ }
375
+
376
+ // src/client/http/fetch-http-client.ts
377
+ var ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:|^\/\//i;
378
+ function resolveUrl(url, baseUrl) {
379
+ if (!baseUrl || ABSOLUTE_URL.test(url)) return url;
380
+ return `${trimTrailingSlashes(baseUrl)}${url.startsWith("/") ? "" : "/"}${url}`;
381
+ }
382
+ function createFetchHttpClient(options = {}) {
383
+ return async (url, init) => {
384
+ const headers = new Headers(await options.headers?.());
385
+ if (!headers.has("Accept")) headers.set("Accept", "application/json");
386
+ for (const [name, value] of Object.entries(init?.headers ?? {})) {
387
+ headers.set(name, value);
388
+ }
389
+ if (init?.body !== void 0 && !headers.has("Content-Type")) {
390
+ headers.set("Content-Type", "application/json");
391
+ }
392
+ let response;
393
+ try {
394
+ response = await fetch(resolveUrl(url, options.baseUrl), {
395
+ method: init?.method ?? "GET",
396
+ headers,
397
+ body: init?.body
398
+ });
399
+ } catch (err) {
400
+ throw markTransportFailure(err);
401
+ }
402
+ return response;
403
+ };
404
+ }
405
+
78
406
  // src/client/types.ts
79
407
  var NOOP_KV_STORE = {
80
408
  get: () => null,
@@ -116,51 +444,326 @@ function resolveLocalStorage() {
116
444
  }
117
445
  }
118
446
  function defaultHttpClient() {
119
- return (url, init) => fetch(url, init);
447
+ return createFetchHttpClient();
120
448
  }
121
449
 
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";
450
+ // src/client/http/axios-http-client.ts
451
+ function stripped(url, prefixes) {
452
+ for (const prefix of prefixes) {
453
+ const trimmed = trimTrailingSlashes(prefix);
454
+ if (!trimmed || !url.startsWith(trimmed)) continue;
455
+ const rest = url.slice(trimmed.length);
456
+ if (rest === "") return "/";
457
+ if (rest.startsWith("/")) return rest;
458
+ if (rest.startsWith("?") || rest.startsWith("#")) return `/${rest}`;
129
459
  }
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;
460
+ return url;
461
+ }
462
+ function headerReader(headers) {
463
+ const record = typeof headers === "object" && headers !== null ? headers : {};
464
+ const lowered = /* @__PURE__ */ new Map();
465
+ for (const [key, value] of Object.entries(record)) lowered.set(key.toLowerCase(), value);
466
+ return (name) => {
467
+ const value = lowered.get(name.toLowerCase());
468
+ return value == null ? null : String(value);
469
+ };
470
+ }
471
+ function bodyIsRaw(response, declared) {
472
+ if (declared !== "auto") return declared === "raw";
473
+ if (response.data === "") return true;
474
+ const config = response.config;
475
+ if (config?.transformResponse === null) return true;
476
+ if (Array.isArray(config?.transformResponse) && config.transformResponse.length === 0) {
477
+ return true;
139
478
  }
479
+ const responseType = config?.responseType;
480
+ if (responseType === "json") return false;
481
+ if (typeof responseType === "string" && responseType !== "") return true;
482
+ const transitional = config?.transitional;
483
+ return typeof transitional === "object" && transitional !== null && transitional.forcedJSONParsing === false;
140
484
  }
141
- function trimTrailingSlashes(url) {
142
- let end = url.length;
143
- while (end > 0 && url[end - 1] === "/") end--;
144
- return url.slice(0, end);
485
+ async function readBody(data) {
486
+ if (typeof data === "string" || data === null || data === void 0) return data;
487
+ if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
488
+ if (ArrayBuffer.isView(data)) return new TextDecoder().decode(data);
489
+ const maybe = data;
490
+ if (typeof maybe.pipe === "function" || typeof maybe.getReader === "function") {
491
+ throw new TypeError(
492
+ "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."
493
+ );
494
+ }
495
+ if (typeof maybe.text === "function") return maybe.text.call(data);
496
+ return data;
145
497
  }
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));
498
+ function adapt(response, declared) {
499
+ return {
500
+ status: response.status,
501
+ headers: { get: headerReader(response.headers) },
502
+ json: async () => {
503
+ const data = await readBody(response.data);
504
+ if (typeof data !== "string") return data;
505
+ return bodyIsRaw({ ...response, data }, declared) ? JSON.parse(data) : data;
506
+ },
507
+ text: async () => {
508
+ const data = await readBody(response.data);
509
+ return typeof data === "string" ? data : JSON.stringify(data);
510
+ }
511
+ };
512
+ }
513
+ function createAxiosHttpClient(instance, options = {}) {
514
+ const prefixes = typeof options.stripPrefix === "string" ? [options.stripPrefix] : options.stripPrefix ?? [];
515
+ const responseBody = options.responseBody ?? "auto";
516
+ return async (url, init) => {
517
+ try {
518
+ return adapt(
519
+ await instance.request({
520
+ url: stripped(url, prefixes),
521
+ method: (init?.method ?? "GET").toUpperCase(),
522
+ headers: init?.headers,
523
+ data: init?.body
524
+ }),
525
+ responseBody
526
+ );
527
+ } catch (err) {
528
+ const response = err?.response;
529
+ if (response && typeof response.status === "number") {
530
+ return adapt(response, responseBody);
531
+ }
532
+ throw isAxiosNoResponseError(err) ? markTransportFailure(err) : err;
533
+ }
534
+ };
535
+ }
536
+
537
+ // src/client/resources/define-resource.ts
538
+ function defineResource(key, ops, options = {}) {
539
+ return { key, ops, projectScoped: options.projectScoped ?? false };
540
+ }
541
+ function requireProjectKey(def, ctx) {
542
+ if (!def.projectScoped) return;
543
+ if (typeof ctx.projectKey === "string" && ctx.projectKey.trim() !== "") return;
544
+ throw new Error(
545
+ `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().`
546
+ );
547
+ }
548
+ function bindResource(def, http, ctx) {
549
+ const readContext = typeof ctx === "function" ? ctx : () => ctx;
550
+ if (def.projectScoped) requireProjectKey(def, readContext());
551
+ const bound = {};
552
+ for (const [name, op] of Object.entries(def.ops)) {
553
+ bound[name] = async (...args) => {
554
+ const context = readContext();
555
+ requireProjectKey(def, context);
556
+ return op(http, context, ...args);
557
+ };
150
558
  }
151
- return await res.json();
559
+ return bound;
152
560
  }
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)
561
+
562
+ // src/client/resources/resource-request.ts
563
+ async function requestJson(http, url, init = {}) {
564
+ const method = init.method ?? "GET";
565
+ const response = await http(url, {
566
+ method,
567
+ headers: { "content-type": "application/json", ...init.headers },
568
+ body: init.body === void 0 ? void 0 : JSON.stringify(init.body)
158
569
  });
159
- if (res.status < 200 || res.status >= 300) {
160
- throw new HttpJsonError(res.status, await extractCode(res));
570
+ requireServerAnswer(
571
+ response.status,
572
+ method,
573
+ url,
574
+ (diagnostic) => new AdminError({ status: response.status, url, method, message: diagnostic })
575
+ );
576
+ if (response.status === 204) return null;
577
+ const body = await response.json().catch(() => null);
578
+ if (response.status >= 400) {
579
+ throw new AdminError({
580
+ status: response.status,
581
+ code: readErrorCode(body),
582
+ body,
583
+ // The third place this had to be read, and the second time it was
584
+ // read a narrower way: without `detail` a page migrating onto these
585
+ // resources loses "Plan already exists" and shows the generic
586
+ // status sentence instead. One decision, one function.
587
+ detail: readErrorDetail(body),
588
+ url,
589
+ method
590
+ });
161
591
  }
162
- return await res.json();
592
+ return body;
593
+ }
594
+ async function requestJsonBody(http, url, what, init = {}) {
595
+ const body = await requestJson(http, url, init);
596
+ if (body === null || body === void 0) {
597
+ throw new AdminError({
598
+ status: 0,
599
+ url,
600
+ method: init.method ?? "GET",
601
+ // `what` is a diagnostic — "Create returned no body" — so it goes
602
+ // on `message`, not `detail`. As `detail` it out-ranked the
603
+ // localized wording and the operator read an English internal
604
+ // string instead of being told the change may already have landed.
605
+ message: what,
606
+ emptyResponse: true
607
+ });
608
+ }
609
+ return body;
610
+ }
611
+
612
+ // src/client/resources/list-resource.ts
613
+ var LIST_FIRST_PAGE = 1;
614
+ var LIST_PAGE_SIZE_DEFAULT = 50;
615
+ var LIST_PAGE_SIZE_MAX = 200;
616
+ var LIST_PAGINATION_PARAMS = ["page", "pageSize"];
617
+ function isSentInQuery(value) {
618
+ return value !== void 0 && value !== null && value !== "";
619
+ }
620
+ function appendFilter(params, filter) {
621
+ for (const [key, value] of Object.entries(filter ?? {})) {
622
+ if (!isSentInQuery(value)) continue;
623
+ params.set(key, String(value));
624
+ }
625
+ }
626
+ function filterQueryString(filter) {
627
+ const params = new URLSearchParams();
628
+ appendFilter(params, filter);
629
+ const query = params.toString();
630
+ return query ? `?${query}` : "";
631
+ }
632
+ function listUrl(endpoint, query = {}) {
633
+ const params = new URLSearchParams();
634
+ params.set("page", String(query.page ?? LIST_FIRST_PAGE));
635
+ params.set("pageSize", String(query.pageSize ?? LIST_PAGE_SIZE_DEFAULT));
636
+ appendFilter(params, query.filter);
637
+ return `${endpoint}${endpoint.includes("?") ? "&" : "?"}${params.toString()}`;
638
+ }
639
+ function readListPage(raw) {
640
+ if (Array.isArray(raw)) return { items: raw, total: raw.length };
641
+ if (raw === null || typeof raw !== "object") return { items: [], total: 0 };
642
+ const body = raw;
643
+ const page = {
644
+ items: Array.isArray(body.items) ? body.items : []
645
+ };
646
+ if (typeof body.total === "number") page.total = body.total;
647
+ if (typeof body.page === "number") page.page = body.page;
648
+ if (typeof body.pageSize === "number") page.pageSize = body.pageSize;
649
+ return page;
650
+ }
651
+ function clampListPage(page) {
652
+ return Math.max(LIST_FIRST_PAGE, Math.floor(page));
163
653
  }
654
+ function clampListPageSize(size) {
655
+ return Math.min(LIST_PAGE_SIZE_MAX, Math.max(1, Math.floor(size)));
656
+ }
657
+ function defineListOp(endpoint) {
658
+ return async (http, ctx, query) => readListPage(await requestJson(http, listUrl(endpoint(ctx), query)));
659
+ }
660
+
661
+ // src/client/resources/plans.resource.ts
662
+ function plansUrl(ctx) {
663
+ return `${ctx.apiBase}/catalog/plans`;
664
+ }
665
+ function versionsUrl(ctx) {
666
+ return `${ctx.apiBase}/catalog/plan-versions`;
667
+ }
668
+ function scoped(url, ctx) {
669
+ return `${url}?projectKey=${encodeURIComponent(ctx.projectKey)}`;
670
+ }
671
+ var plansResource = defineResource(
672
+ "plans",
673
+ {
674
+ list: async (http, ctx) => await requestJson(http, scoped(plansUrl(ctx), ctx)) ?? [],
675
+ tenantCounts: async (http, ctx) => await requestJson(
676
+ http,
677
+ scoped(`${plansUrl(ctx)}/tenant-counts`, ctx)
678
+ ) ?? {},
679
+ /**
680
+ * The project comes from the context, not from the caller.
681
+ *
682
+ * The endpoint takes it in the body, and the composable this replaces
683
+ * made that the caller's job — so a page had to keep hold of the
684
+ * project it was administering just to create in it, and `list()` and
685
+ * `create()` read two different sources for one value. A stale one
686
+ * creates a plan outside the project the list is showing.
687
+ */
688
+ create: (http, ctx, data) => requestJsonBody(http, plansUrl(ctx), "Create returned no body", {
689
+ method: "POST",
690
+ body: { ...data, projectKey: ctx.projectKey }
691
+ }),
692
+ update: (http, ctx, planId, data) => requestJsonBody(
693
+ http,
694
+ `${plansUrl(ctx)}/${planId}`,
695
+ "Update returned no body",
696
+ {
697
+ method: "PATCH",
698
+ body: data
699
+ }
700
+ ),
701
+ /** Marks the plan deleted. Fails while it still has versions. */
702
+ softDelete: async (http, ctx, planId) => {
703
+ await requestJson(http, `${plansUrl(ctx)}/${planId}`, { method: "DELETE" });
704
+ },
705
+ /** Removes the plan for good. 422 `PLAN_HAS_VERSIONS` when it cannot. */
706
+ hardDelete: async (http, ctx, planId) => {
707
+ await requestJson(http, `${plansUrl(ctx)}/${planId}/purge`, { method: "DELETE" });
708
+ }
709
+ },
710
+ { projectScoped: true }
711
+ );
712
+ var planVersionsResource = defineResource("planVersions", {
713
+ listForPlan: async (http, ctx, planId) => await requestJson(http, `${plansUrl(ctx)}/${planId}/versions`) ?? [],
714
+ createDraft: (http, ctx, planId, data) => requestJsonBody(
715
+ http,
716
+ `${plansUrl(ctx)}/${planId}/versions`,
717
+ "CreateDraft returned no body",
718
+ { method: "POST", body: data }
719
+ ),
720
+ updateDraft: (http, ctx, versionId, data) => requestJsonBody(
721
+ http,
722
+ `${versionsUrl(ctx)}/${versionId}`,
723
+ "UpdateDraft returned no body",
724
+ { method: "PATCH", body: data }
725
+ ),
726
+ publish: (http, ctx, versionId, options = {}) => requestJsonBody(
727
+ http,
728
+ `${versionsUrl(ctx)}/${versionId}/publish`,
729
+ "Publish returned no body",
730
+ { method: "POST", body: options }
731
+ ),
732
+ discardDraft: async (http, ctx, versionId) => {
733
+ await requestJson(http, `${versionsUrl(ctx)}/${versionId}`, { method: "DELETE" });
734
+ },
735
+ /**
736
+ * Ends a live version on `endsAt`, with no successor. Idempotent — a
737
+ * second call with another date overwrites the first.
738
+ *
739
+ * `endsAt` is the date itself, not a payload: the endpoint's body is
740
+ * `{ endsAt }` and wrapping it is this operation's job, not the caller's.
741
+ */
742
+ terminate: (http, ctx, versionId, endsAt) => requestJsonBody(
743
+ http,
744
+ `${versionsUrl(ctx)}/${versionId}/terminate`,
745
+ "Terminate returned no body",
746
+ { method: "POST", body: { endsAt } }
747
+ )
748
+ });
749
+
750
+ // src/client/resources/tenants.resource.ts
751
+ var tenantsResource = defineResource("tenants", {
752
+ list: defineListOp((ctx) => `${ctx.apiBase}/tenants`)
753
+ });
754
+
755
+ // src/client/resources/audit.resource.ts
756
+ var auditResource = defineResource("audit", {
757
+ list: defineListOp((ctx) => `${ctx.apiBase}/audit`)
758
+ });
759
+
760
+ // src/client/resources/index.ts
761
+ var platformResources = {
762
+ plans: plansResource,
763
+ planVersions: planVersionsResource,
764
+ tenants: tenantsResource,
765
+ audit: auditResource
766
+ };
164
767
 
165
768
  // src/client/boot-loader.ts
166
769
  var BootLoadError = class extends Error {
@@ -168,6 +771,7 @@ var BootLoadError = class extends Error {
168
771
  super(message);
169
772
  this.status = status;
170
773
  this.name = "BootLoadError";
774
+ markPlatformError(this);
171
775
  }
172
776
  status;
173
777
  };
@@ -185,19 +789,27 @@ var BootLoader = class {
185
789
  }
186
790
  async load() {
187
791
  const res = await this.http(this.endpoint);
792
+ requireServerAnswer(
793
+ res.status,
794
+ "GET",
795
+ this.endpoint,
796
+ (diagnostic) => new BootLoadError(res.status, diagnostic)
797
+ );
188
798
  if (res.status !== 200) {
189
- throw new BootLoadError(res.status, `Boot-Endpunkt antwortete HTTP ${res.status}`);
799
+ throw new BootLoadError(res.status, `Boot endpoint responded with HTTP ${res.status}`);
190
800
  }
191
801
  return await res.json();
192
802
  }
193
803
  };
194
804
 
195
805
  // src/client/manifest-loader.ts
806
+ var NOT_MODIFIED = 304;
196
807
  var ManifestLoadError = class extends Error {
197
808
  constructor(status, message) {
198
809
  super(message);
199
810
  this.status = status;
200
811
  this.name = "ManifestLoadError";
812
+ markPlatformError(this);
201
813
  }
202
814
  status;
203
815
  };
@@ -225,24 +837,48 @@ var ManifestLoader = class {
225
837
  /**
226
838
  * Loads the current manifest. On a cache hit (304) the cached
227
839
  * body is returned — otherwise the fresh server body.
840
+ *
841
+ * A 304 with no usable cached body is not a failure to report but one to
842
+ * undo: the request only carried `If-None-Match` because this loader put
843
+ * the ETag there, and the cache it validated against is this loader's own.
844
+ * So it drops the pair and asks once more without the header, which is the
845
+ * same recovery a person would have had to trigger by hand — except that
846
+ * the person reading an admin screen has neither this loader nor a
847
+ * console. Exactly one retry: a 304 to a request that asked nothing
848
+ * conditional is a server fault, and no repetition fixes it.
228
849
  */
229
850
  async load() {
230
851
  const cachedEtag = this.storage.get(this.etagKey);
852
+ const res = await this.request(cachedEtag);
853
+ if (res.status !== NOT_MODIFIED) return this.acceptFresh(res);
854
+ const cached = this.readCachedBody();
855
+ if (cached) return cached.body;
856
+ if (cachedEtag) {
857
+ this.clearCache();
858
+ const fresh = await this.request(null);
859
+ if (fresh.status !== NOT_MODIFIED) return this.acceptFresh(fresh);
860
+ }
861
+ throw new ManifestLoadError(
862
+ NOT_MODIFIED,
863
+ "Manifest endpoint answered HTTP 304 to a request without If-None-Match"
864
+ );
865
+ }
866
+ /** Sends the manifest GET, conditional when an ETag is supplied. */
867
+ request(etag) {
231
868
  const headers = {};
232
869
  const token = this.getAuthToken?.();
233
870
  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
- }
871
+ if (etag) headers["If-None-Match"] = etag;
872
+ return this.http(this.endpoint, { method: "GET", headers });
873
+ }
874
+ /** Takes a non-304 response as the current manifest and caches it. */
875
+ async acceptFresh(res) {
876
+ requireServerAnswer(
877
+ res.status,
878
+ "GET",
879
+ this.endpoint,
880
+ (diagnostic) => new ManifestLoadError(res.status, diagnostic)
881
+ );
246
882
  if (res.status !== 200) {
247
883
  throw new ManifestLoadError(
248
884
  res.status,
@@ -378,7 +1014,7 @@ var DEFAULT_STANDARD_PAGE_ROUTES = {
378
1014
  platformEmailHistory: "/admin/platform-email-history"
379
1015
  };
380
1016
  function isSupportedStandardPageKey(key) {
381
- return Object.hasOwn(DEFAULT_STANDARD_PAGE_ROUTES, key);
1017
+ return Object.prototype.hasOwnProperty.call(DEFAULT_STANDARD_PAGE_ROUTES, key);
382
1018
  }
383
1019
  var DEFAULT_ICONS = {
384
1020
  dashboard: "dashboard",
@@ -577,7 +1213,7 @@ var ActionRegistry = class {
577
1213
  // src/client/batch-column-fetcher.ts
578
1214
  var BatchColumnDriftError = class extends Error {
579
1215
  constructor(column, reason) {
580
- super(`Spalte "${column.key}": ${reason}`);
1216
+ super(`Column "${column.key}": ${reason}`);
581
1217
  this.column = column;
582
1218
  this.name = "BatchColumnDriftError";
583
1219
  }
@@ -620,7 +1256,7 @@ var BatchColumnFetcher = class {
620
1256
  const res = await this.http(url, { method: "GET", headers });
621
1257
  if (res.status !== 200) {
622
1258
  throw new Error(
623
- `Spalte "${column.key}" \u2014 Endpoint ${column.endpoint} antwortete HTTP ${res.status}`
1259
+ `Column "${column.key}" \u2014 endpoint ${column.endpoint} responded with HTTP ${res.status}`
624
1260
  );
625
1261
  }
626
1262
  const body = await res.json();
@@ -696,10 +1332,10 @@ function createAdminResourceClient(options) {
696
1332
  tenantsEndpoint,
697
1333
  loadTenantDetail: (slug) => getJson2(`${tenantsEndpoint}/${encodeURIComponent(slug)}`),
698
1334
  loadUsers: (filter) => getJson2(
699
- `${base}/users${queryString({ q: filter.q, tenant: filter.tenant })}`
1335
+ `${base}/users${filterQueryString({ q: filter.q, tenant: filter.tenant })}`
700
1336
  ),
701
1337
  loadAudit: (filter) => getJson2(
702
- `${base}/audit${queryString({
1338
+ `${base}/audit${filterQueryString({
703
1339
  actor: filter.actor,
704
1340
  action: filter.action,
705
1341
  entity: filter.entity,
@@ -709,7 +1345,7 @@ function createAdminResourceClient(options) {
709
1345
  ),
710
1346
  loadSubscriptions: () => getJson2(`${base}/subscriptions`),
711
1347
  loadPromos: (filter) => getJson2(
712
- `${base}/promo-codes${queryString({
1348
+ `${base}/promo-codes${filterQueryString({
713
1349
  search: filter.search,
714
1350
  status: filter.status
715
1351
  })}`
@@ -734,21 +1370,6 @@ function assertSuccess(status, method, url) {
734
1370
  throw new Error(`${method} ${url} \u2192 HTTP ${status}`);
735
1371
  }
736
1372
  }
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
1373
 
753
1374
  // src/client/i18n/currency.ts
754
1375
  var DEFAULT_CURRENCY = "EUR";
@@ -1447,9 +2068,6 @@ var discoveryMessages = defineMessages(
1447
2068
  replacedBy: "ersetzt durch {key}",
1448
2069
  replaces: "ersetzt: {keys}",
1449
2070
  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
2071
  capList: {
1454
2072
  orphanFeature: "Feature im Katalog ohne implementierende Capability \u2014 blockiert im blocking-Strict-Mode das Plan-Publish.",
1455
2073
  removedFromCode: "aus Code entfernt"
@@ -1551,9 +2169,6 @@ var discoveryMessages = defineMessages(
1551
2169
  replacedBy: "replaced by {key}",
1552
2170
  replaces: "replaces: {keys}",
1553
2171
  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
2172
  capList: {
1558
2173
  orphanFeature: "Feature in the catalog without an implementing capability \u2014 blocks plan publishing in blocking strict mode.",
1559
2174
  removedFromCode: "removed from code"
@@ -1758,6 +2373,36 @@ var emailMessages = defineMessages(
1758
2373
  }
1759
2374
  );
1760
2375
 
2376
+ // src/client/i18n/messages/errors.ts
2377
+ var errorsMessages = defineMessages(
2378
+ {
2379
+ network: "Die Anfrage konnte nicht abgeschlossen werden. Bitte Verbindung pr\xFCfen.",
2380
+ emptyResponse: "Der Server hat nichts zur\xFCckgegeben. Bitte pr\xFCfen, ob die \xC4nderung angekommen ist.",
2381
+ unauthorized: "Die Sitzung ist abgelaufen. Bitte erneut anmelden.",
2382
+ forbidden: "F\xFCr diese Aktion fehlt die Berechtigung.",
2383
+ notFound: "Der angeforderte Eintrag existiert nicht mehr.",
2384
+ conflict: "Der Eintrag wurde zwischenzeitlich ge\xE4ndert. Bitte neu laden.",
2385
+ validation: "Die Eingaben wurden nicht akzeptiert.",
2386
+ rateLimited: "Zu viele Anfragen. Bitte kurz warten und erneut versuchen.",
2387
+ server: "Der Server konnte die Anfrage nicht verarbeiten.",
2388
+ httpStatus: "Die Anfrage ist fehlgeschlagen (HTTP {status}).",
2389
+ unexpected: "Es ist ein unerwarteter Fehler aufgetreten. Bitte erneut versuchen."
2390
+ },
2391
+ {
2392
+ network: "The request could not be completed. Please check your connection.",
2393
+ emptyResponse: "The server returned nothing. Please check whether the change was applied.",
2394
+ unauthorized: "Your session has expired. Please sign in again.",
2395
+ forbidden: "You do not have permission for this action.",
2396
+ notFound: "The requested entry no longer exists.",
2397
+ conflict: "The entry changed in the meantime. Please reload.",
2398
+ validation: "The input was not accepted.",
2399
+ rateLimited: "Too many requests. Please wait a moment and try again.",
2400
+ server: "The server could not process the request.",
2401
+ httpStatus: "The request failed (HTTP {status}).",
2402
+ unexpected: "Something went wrong. Please try again."
2403
+ }
2404
+ );
2405
+
1761
2406
  // src/client/i18n/messages/marketing.ts
1762
2407
  var marketingMessages = defineMessages(
1763
2408
  {
@@ -1882,9 +2527,6 @@ var marketingMessages = defineMessages(
1882
2527
  colorLabel: "Farbe",
1883
2528
  delete: "Aktion l\xF6schen",
1884
2529
  deleteConfirm: "Aktion \u201E{label}\u201C wirklich l\xF6schen?"
1885
- },
1886
- errors: {
1887
- projectionsApi: "MarketingProjections-API antwortete mit HTTP {status}"
1888
2530
  }
1889
2531
  },
1890
2532
  {
@@ -2009,9 +2651,6 @@ var marketingMessages = defineMessages(
2009
2651
  colorLabel: "Color",
2010
2652
  delete: "Delete promotion",
2011
2653
  deleteConfirm: 'Really delete promotion "{label}"?'
2012
- },
2013
- errors: {
2014
- projectionsApi: "MarketingProjections API responded with HTTP {status}"
2015
2654
  }
2016
2655
  }
2017
2656
  );
@@ -3205,7 +3844,6 @@ var promosMessages = defineMessages(
3205
3844
  {
3206
3845
  title: "Promo-Codes",
3207
3846
  createAction: "Promo-Code anlegen",
3208
- apiErrorHttpStatus: "Promotions-API antwortete mit HTTP {status}",
3209
3847
  list: {
3210
3848
  searchLabel: "Suche (Code)",
3211
3849
  tileScheduled: "Geplant",
@@ -3303,7 +3941,6 @@ var promosMessages = defineMessages(
3303
3941
  {
3304
3942
  title: "Promo codes",
3305
3943
  createAction: "Create promo code",
3306
- apiErrorHttpStatus: "Promotions API responded with HTTP {status}",
3307
3944
  list: {
3308
3945
  searchLabel: "Search (code)",
3309
3946
  tileScheduled: "Scheduled",
@@ -3411,7 +4048,13 @@ var shellMessages = defineMessages(
3411
4048
  subtitle: "SuperAdmin \xB7 Plattform-Verwaltung",
3412
4049
  roleBadge: "SUPER ADMIN",
3413
4050
  logout: "Abmelden",
3414
- language: "Sprache"
4051
+ language: "Sprache",
4052
+ theme: {
4053
+ label: "Darstellung",
4054
+ light: "Hell",
4055
+ dark: "Dunkel",
4056
+ system: "System"
4057
+ }
3415
4058
  },
3416
4059
  drawer: {
3417
4060
  docs: "Doku \xF6ffnen"
@@ -3480,7 +4123,13 @@ var shellMessages = defineMessages(
3480
4123
  subtitle: "SuperAdmin \xB7 Platform administration",
3481
4124
  roleBadge: "SUPER ADMIN",
3482
4125
  logout: "Sign out",
3483
- language: "Language"
4126
+ language: "Language",
4127
+ theme: {
4128
+ label: "Appearance",
4129
+ light: "Light",
4130
+ dark: "Dark",
4131
+ system: "System"
4132
+ }
3484
4133
  },
3485
4134
  drawer: {
3486
4135
  docs: "Open documentation"
@@ -3724,6 +4373,7 @@ var usersMessages = defineMessages(
3724
4373
  function catalogFor(locale) {
3725
4374
  return {
3726
4375
  common: commonMessages[locale],
4376
+ errors: errorsMessages[locale],
3727
4377
  nav: navMessages[locale],
3728
4378
  shell: shellMessages[locale],
3729
4379
  dashboard: dashboardMessages[locale],
@@ -3912,6 +4562,7 @@ function identityChipStyle(accent) {
3912
4562
  ADMIN_UI_VERSION,
3913
4563
  ActionDefNotInManifestError,
3914
4564
  ActionRegistry,
4565
+ AdminError,
3915
4566
  BatchColumnDriftError,
3916
4567
  BatchColumnFetcher,
3917
4568
  BootLoadError,
@@ -3923,6 +4574,10 @@ function identityChipStyle(accent) {
3923
4574
  IDENTITY_ACCENT_VALUES,
3924
4575
  IDENTITY_NEUTRAL,
3925
4576
  IDENTITY_NEUTRAL_VALUE,
4577
+ LIST_FIRST_PAGE,
4578
+ LIST_PAGE_SIZE_DEFAULT,
4579
+ LIST_PAGE_SIZE_MAX,
4580
+ LIST_PAGINATION_PARAMS,
3926
4581
  ManifestLoadError,
3927
4582
  ManifestLoader,
3928
4583
  MissingHandlerError,
@@ -3930,32 +4585,63 @@ function identityChipStyle(accent) {
3930
4585
  SA_LOCALES,
3931
4586
  SA_LOCALE_LABELS,
3932
4587
  SA_MESSAGES,
4588
+ adminErrorMessage,
4589
+ auditResource,
4590
+ bindResource,
3933
4591
  buildRoutes,
3934
4592
  buildSidebar,
4593
+ clampListPage,
4594
+ clampListPageSize,
3935
4595
  countPlans,
3936
4596
  createAdminResourceClient,
4597
+ createAxiosHttpClient,
4598
+ createFetchHttpClient,
3937
4599
  createSaCatalog,
3938
4600
  defaultHttpClient,
3939
4601
  defaultKvStore,
3940
4602
  defaultSectionOrder,
4603
+ defineListOp,
3941
4604
  defineMessages,
4605
+ defineResource,
4606
+ filterQueryString,
3942
4607
  formatCurrency,
3943
4608
  formatMessage,
3944
4609
  getJson,
3945
4610
  identityAccentAt,
3946
4611
  identityAccentFor,
3947
4612
  identityChipStyle,
4613
+ isAdminError,
4614
+ isAxiosNoResponseError,
3948
4615
  isCurrentlyValid,
4616
+ isEmptyResponse,
3949
4617
  isExpired,
3950
4618
  isFutureScheduled,
4619
+ isPlatformError,
3951
4620
  isProductionBoot,
3952
4621
  isSaBuiltinLocale,
4622
+ isSentInQuery,
4623
+ isTransportFailure,
4624
+ listUrl,
4625
+ markEmptyResponse,
4626
+ markPlatformError,
4627
+ markTransportFailure,
3953
4628
  mergeMessages,
4629
+ planVersionsResource,
4630
+ plansResource,
4631
+ platformResources,
3954
4632
  postJson,
4633
+ readErrorCode,
4634
+ readErrorDetail,
4635
+ readListPage,
4636
+ requestJson,
4637
+ requestJsonBody,
4638
+ requireServerAnswer,
3955
4639
  resolveExtension,
3956
4640
  resolveLoginBranding,
3957
4641
  resolveMessages,
3958
4642
  resolvePlans,
4643
+ tenantsResource,
4644
+ toAdminError,
3959
4645
  todayIsoDate,
3960
4646
  trimTrailingSlashes
3961
4647
  });