@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.
- package/README.md +32 -8
- package/dist/{catalog-DJcfm5N2.d.cts → catalog-Dch1Ryw0.d.cts} +353 -69
- package/dist/{catalog-DJcfm5N2.d.ts → catalog-Dch1Ryw0.d.ts} +353 -69
- package/dist/{chunk-F7NRM6KI.js → chunk-BPF2BMCQ.js} +748 -24
- package/dist/{chunk-D74FFCCP.js → chunk-NEXTZZRQ.js} +95 -177
- package/dist/{chunk-TQQMKWN6.js → chunk-O3J3ITF2.js} +119 -7
- package/dist/client/index.cjs +758 -83
- package/dist/client/index.d.cts +414 -9
- package/dist/client/index.d.ts +414 -9
- package/dist/client/index.js +84 -12
- package/dist/index.cjs +1325 -294
- package/dist/index.d.cts +205 -15
- package/dist/index.d.ts +205 -15
- package/dist/index.js +566 -240
- package/dist/quasar/index.cjs +574 -62
- package/dist/quasar/index.d.cts +61 -6
- package/dist/quasar/index.d.ts +61 -6
- package/dist/quasar/index.js +83 -19
- package/dist/{use-sa-theme-OUBlaqgl.d.ts → resource-registry-D7Xjs-5f.d.cts} +157 -7
- package/dist/{use-sa-theme-B5t7oXph.d.cts → resource-registry-TaR7rtq0.d.ts} +157 -7
- package/package.json +4 -3
- package/src/client/admin-error.ts +552 -0
- package/src/client/admin-resource-client.ts +9 -12
- package/src/client/batch-column-fetcher.ts +2 -2
- package/src/client/boot-loader.ts +12 -1
- package/src/client/http/axios-http-client.ts +383 -0
- package/src/client/http/fetch-http-client.ts +89 -0
- package/src/client/http/index.ts +6 -0
- package/src/client/http-json.ts +82 -16
- package/src/client/i18n/messages/discovery.ts +0 -6
- package/src/client/i18n/messages/errors.ts +44 -0
- package/src/client/i18n/messages/marketing.ts +0 -6
- package/src/client/i18n/messages/promos.ts +0 -2
- package/src/client/i18n/messages/shell.ts +12 -0
- package/src/client/i18n/messages.ts +3 -0
- package/src/client/index.ts +6 -0
- package/src/client/manifest-loader.ts +65 -15
- package/src/client/resources/audit.resource.ts +25 -0
- package/src/client/resources/define-resource.ts +166 -0
- package/src/client/resources/index.ts +42 -0
- package/src/client/resources/list-resource.ts +176 -0
- package/src/client/resources/plans.resource.ts +169 -0
- package/src/client/resources/resource-request.ts +117 -0
- package/src/client/resources/tenants.resource.ts +27 -0
- package/src/client/types.ts +10 -3
- package/src/components/BundleVersionPublishDialog.vue +1 -1
- package/src/components/MarketingPromotionsTab.vue +263 -241
- package/src/components/ThemeSwitcher.vue +85 -0
- package/src/components/admin-page/AdminAccordion.vue +125 -0
- package/src/components/bundle-editor/BundleCreatePanel.vue +1 -1
- package/src/components/dialogs/PromoCodeDialogFields.vue +20 -20
- package/src/components/plan/PlanCycleToggle.vue +1 -1
- package/src/components/plan-detail/PlanVersionsPanel.vue +5 -2
- package/src/index.ts +5 -0
- package/src/pages-standard/AdminLayout.vue +57 -0
- package/src/pages-standard/AdminManifestErrorPage.vue +4 -3
- package/src/pages-standard/BundlesPage.vue +5 -35
- package/src/pages-standard/DiscoveryPage.vue +8 -1
- package/src/pages-standard/MarketingCatalogPage.vue +14 -3
- package/src/pages-standard/SuperAdminLoginPage.vue +23 -2
- package/src/pages-standard/SuperAdminSetupWizard.vue +37 -3
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +30 -32
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +80 -88
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +73 -79
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +44 -6
- package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +8 -2
- package/src/pages-tenant/PackageSnapshotPanel.vue +31 -2
- package/src/quasar/confirm.ts +83 -0
- package/src/quasar/create-super-admin-app.ts +84 -10
- package/src/quasar/dark-bridge.ts +47 -10
- package/src/quasar/index.ts +1 -0
- package/src/ui/theme/components/accordion.css +118 -0
- package/src/ui/theme/index.css +1 -0
- package/src/vue/create-admin-routes.ts +9 -1
- package/src/vue/platform-loaders.ts +12 -2
- package/src/vue/resource-registry.ts +211 -0
- package/src/vue/super-admin-context.ts +20 -5
- package/src/vue/ui-confirm.ts +76 -0
- package/src/vue/use-api-list.ts +50 -45
- package/src/vue/use-async-action.ts +170 -0
- package/src/vue/use-async-data.ts +83 -0
- package/src/vue/use-bundles.ts +34 -7
- package/src/vue/use-catalog-entries.ts +37 -12
- package/src/vue/use-discovery.ts +27 -6
- package/src/vue/use-marketing-projections.ts +29 -7
- package/src/vue/use-plans.ts +36 -8
- package/src/vue/use-promotions.ts +27 -7
- package/src/vue/use-resource-list.ts +272 -0
- package/src/vue/use-sa-theme.ts +34 -5
- package/src/vue/use-tenant-subscription-bundles.ts +22 -3
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(src_exports, {
|
|
|
24
24
|
ALL_ROWS: () => ALL_ROWS,
|
|
25
25
|
ActionDefNotInManifestError: () => ActionDefNotInManifestError,
|
|
26
26
|
ActionRegistry: () => ActionRegistry,
|
|
27
|
+
AdminError: () => AdminError,
|
|
27
28
|
BatchColumnDriftError: () => BatchColumnDriftError,
|
|
28
29
|
BatchColumnFetcher: () => BatchColumnFetcher,
|
|
29
30
|
BootLoadError: () => BootLoadError,
|
|
@@ -44,6 +45,10 @@ __export(src_exports, {
|
|
|
44
45
|
IDENTITY_ACCENT_VALUES: () => IDENTITY_ACCENT_VALUES,
|
|
45
46
|
IDENTITY_NEUTRAL: () => IDENTITY_NEUTRAL,
|
|
46
47
|
IDENTITY_NEUTRAL_VALUE: () => IDENTITY_NEUTRAL_VALUE,
|
|
48
|
+
LIST_FIRST_PAGE: () => LIST_FIRST_PAGE,
|
|
49
|
+
LIST_PAGE_SIZE_DEFAULT: () => LIST_PAGE_SIZE_DEFAULT,
|
|
50
|
+
LIST_PAGE_SIZE_MAX: () => LIST_PAGE_SIZE_MAX,
|
|
51
|
+
LIST_PAGINATION_PARAMS: () => LIST_PAGINATION_PARAMS,
|
|
47
52
|
ManifestLoadError: () => ManifestLoadError,
|
|
48
53
|
ManifestLoader: () => ManifestLoader,
|
|
49
54
|
MarketingProjectionsApiError: () => MarketingProjectionsApiError,
|
|
@@ -53,15 +58,18 @@ __export(src_exports, {
|
|
|
53
58
|
PlansApiError: () => PlansApiError,
|
|
54
59
|
ProjectPageHost: () => ProjectPageHost,
|
|
55
60
|
PromotionsApiError: () => PromotionsApiError,
|
|
61
|
+
SA_COLOR_SCHEMES: () => SA_COLOR_SCHEMES,
|
|
56
62
|
SA_INTL_LOCALES: () => SA_INTL_LOCALES,
|
|
57
63
|
SA_LOCALES: () => SA_LOCALES,
|
|
58
64
|
SA_LOCALE_LABELS: () => SA_LOCALE_LABELS,
|
|
59
65
|
SA_LOCALE_STORAGE_KEY: () => SA_LOCALE_STORAGE_KEY,
|
|
60
66
|
SA_MESSAGES: () => SA_MESSAGES,
|
|
61
67
|
SA_THEME_KEY: () => SA_THEME_KEY,
|
|
68
|
+
SA_THEME_STORAGE_KEY: () => SA_THEME_STORAGE_KEY,
|
|
62
69
|
SERVER_PAGE_SIZE_OPTIONS: () => SERVER_PAGE_SIZE_OPTIONS,
|
|
63
70
|
SUPER_ADMIN_ACTIONS_KEY: () => SUPER_ADMIN_ACTIONS_KEY,
|
|
64
71
|
SUPER_ADMIN_BRAND_KEY: () => SUPER_ADMIN_BRAND_KEY,
|
|
72
|
+
SUPER_ADMIN_CONFIRM_KEY: () => SUPER_ADMIN_CONFIRM_KEY,
|
|
65
73
|
SUPER_ADMIN_ENDPOINTS_KEY: () => SUPER_ADMIN_ENDPOINTS_KEY,
|
|
66
74
|
SUPER_ADMIN_EXTENSIONS_KEY: () => SUPER_ADMIN_EXTENSIONS_KEY,
|
|
67
75
|
SUPER_ADMIN_HTTP_KEY: () => SUPER_ADMIN_HTTP_KEY,
|
|
@@ -70,19 +78,28 @@ __export(src_exports, {
|
|
|
70
78
|
SUPER_ADMIN_MANIFEST_CLEAR_CACHE_KEY: () => SUPER_ADMIN_MANIFEST_CLEAR_CACHE_KEY,
|
|
71
79
|
SUPER_ADMIN_MANIFEST_KEY: () => SUPER_ADMIN_MANIFEST_KEY,
|
|
72
80
|
SUPER_ADMIN_NOTIFY_KEY: () => SUPER_ADMIN_NOTIFY_KEY,
|
|
81
|
+
SUPER_ADMIN_RESOURCES_KEY: () => SUPER_ADMIN_RESOURCES_KEY,
|
|
73
82
|
TenantSubscriptionBundlesApiError: () => TenantSubscriptionBundlesApiError,
|
|
83
|
+
adminErrorMessage: () => adminErrorMessage,
|
|
84
|
+
auditResource: () => auditResource,
|
|
85
|
+
bindResource: () => bindResource,
|
|
74
86
|
buildFeatureRegistry: () => buildFeatureRegistry,
|
|
75
87
|
buildFeatureRouterGuard: () => buildFeatureRouterGuard,
|
|
76
88
|
buildNavigationGuard: () => buildNavigationGuard,
|
|
77
89
|
buildQuotaRegistry: () => buildQuotaRegistry,
|
|
78
90
|
buildRoutes: () => buildRoutes,
|
|
79
91
|
buildSidebar: () => buildSidebar,
|
|
92
|
+
clampListPage: () => clampListPage,
|
|
93
|
+
clampListPageSize: () => clampListPageSize,
|
|
80
94
|
countPlans: () => countPlans,
|
|
81
95
|
createAdminResourceClient: () => createAdminResourceClient,
|
|
82
96
|
createAdminRoutes: () => createAdminRoutes,
|
|
97
|
+
createAxiosHttpClient: () => createAxiosHttpClient,
|
|
98
|
+
createFetchHttpClient: () => createFetchHttpClient,
|
|
83
99
|
createManifestStore: () => createManifestStore,
|
|
84
100
|
createPlatformLoaders: () => createPlatformLoaders,
|
|
85
101
|
createProjectPageHostRoute: () => createProjectPageHostRoute,
|
|
102
|
+
createResourceRegistry: () => createResourceRegistry,
|
|
86
103
|
createSaCatalog: () => createSaCatalog,
|
|
87
104
|
createSaTheme: () => createSaTheme,
|
|
88
105
|
createSuperAdminI18n: () => createSuperAdminI18n,
|
|
@@ -92,30 +109,57 @@ __export(src_exports, {
|
|
|
92
109
|
defaultSectionOrder: () => defaultSectionOrder,
|
|
93
110
|
defaultTenantPlanSectionI18n: () => defaultTenantPlanSectionI18n,
|
|
94
111
|
defaultToneForActionKey: () => defaultToneForActionKey,
|
|
112
|
+
defineListOp: () => defineListOp,
|
|
95
113
|
defineMessages: () => defineMessages,
|
|
114
|
+
defineResource: () => defineResource,
|
|
115
|
+
filterQueryString: () => filterQueryString,
|
|
96
116
|
formatCurrency: () => formatCurrency,
|
|
97
117
|
formatMessage: () => formatMessage,
|
|
98
118
|
getJson: () => getJson,
|
|
99
119
|
identityAccentAt: () => identityAccentAt,
|
|
100
120
|
identityAccentFor: () => identityAccentFor,
|
|
101
121
|
identityChipStyle: () => identityChipStyle,
|
|
122
|
+
isAdminError: () => isAdminError,
|
|
123
|
+
isAxiosNoResponseError: () => isAxiosNoResponseError,
|
|
102
124
|
isCurrentlyValid: () => isCurrentlyValid,
|
|
125
|
+
isEmptyResponse: () => isEmptyResponse,
|
|
103
126
|
isExpired: () => isExpired,
|
|
104
127
|
isFutureScheduled: () => isFutureScheduled,
|
|
128
|
+
isPlatformError: () => isPlatformError,
|
|
105
129
|
isProductionBoot: () => isProductionBoot,
|
|
106
130
|
isSaBuiltinLocale: () => isSaBuiltinLocale,
|
|
131
|
+
isSentInQuery: () => isSentInQuery,
|
|
132
|
+
isTransportFailure: () => isTransportFailure,
|
|
133
|
+
listUrl: () => listUrl,
|
|
134
|
+
markEmptyResponse: () => markEmptyResponse,
|
|
135
|
+
markPlatformError: () => markPlatformError,
|
|
136
|
+
markTransportFailure: () => markTransportFailure,
|
|
107
137
|
mergeMessages: () => mergeMessages,
|
|
108
138
|
planChangeWizardI18n: () => planChangeWizardI18n,
|
|
139
|
+
planVersionsResource: () => planVersionsResource,
|
|
140
|
+
plansResource: () => plansResource,
|
|
141
|
+
platformResources: () => platformResources,
|
|
109
142
|
postJson: () => postJson,
|
|
110
143
|
provideEntitlement: () => provideEntitlement,
|
|
144
|
+
readErrorCode: () => readErrorCode,
|
|
145
|
+
readErrorDetail: () => readErrorDetail,
|
|
146
|
+
readListPage: () => readListPage,
|
|
147
|
+
requestJson: () => requestJson,
|
|
148
|
+
requestJsonBody: () => requestJsonBody,
|
|
149
|
+
requireServerAnswer: () => requireServerAnswer,
|
|
111
150
|
resolveExtension: () => resolveExtension,
|
|
112
151
|
resolveLoginBranding: () => resolveLoginBranding,
|
|
113
152
|
resolveMessages: () => resolveMessages,
|
|
114
153
|
resolvePlans: () => resolvePlans,
|
|
154
|
+
resolveSuperAdminEndpoints: () => resolveSuperAdminEndpoints,
|
|
155
|
+
tenantsResource: () => tenantsResource,
|
|
156
|
+
toAdminError: () => toAdminError,
|
|
115
157
|
todayIsoDate: () => todayIsoDate,
|
|
116
158
|
trimTrailingSlashes: () => trimTrailingSlashes,
|
|
117
159
|
useActions: () => useActions,
|
|
118
160
|
useApiList: () => useApiList,
|
|
161
|
+
useAsyncAction: () => useAsyncAction,
|
|
162
|
+
useAsyncData: () => useAsyncData,
|
|
119
163
|
useAuditEntries: () => useAuditEntries,
|
|
120
164
|
useBatchColumns: () => useBatchColumns,
|
|
121
165
|
useBulkPublish: () => useBulkPublish,
|
|
@@ -137,6 +181,8 @@ __export(src_exports, {
|
|
|
137
181
|
usePlatformTenantActions: () => usePlatformTenantActions,
|
|
138
182
|
usePromotions: () => usePromotions,
|
|
139
183
|
usePublicBoot: () => usePublicBoot,
|
|
184
|
+
useResource: () => useResource,
|
|
185
|
+
useResourceList: () => useResourceList,
|
|
140
186
|
useSaMessages: () => useSaMessages,
|
|
141
187
|
useSaTheme: () => useSaTheme,
|
|
142
188
|
useSubscriptionDraft: () => useSubscriptionDraft,
|
|
@@ -160,6 +206,287 @@ module.exports = __toCommonJS(src_exports);
|
|
|
160
206
|
// src/client/version.ts
|
|
161
207
|
var ADMIN_UI_VERSION = "1.2.0";
|
|
162
208
|
|
|
209
|
+
// src/client/i18n/format.ts
|
|
210
|
+
var import_types = require("@saasicat/types");
|
|
211
|
+
function formatMessage(template, params) {
|
|
212
|
+
return (0, import_types.formatErrorMessage)(template, params);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/client/admin-error.ts
|
|
216
|
+
var ADMIN_ERROR = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/AdminError");
|
|
217
|
+
var PLATFORM_ERROR = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/PlatformError");
|
|
218
|
+
function markPlatformError(error) {
|
|
219
|
+
Object.defineProperty(error, PLATFORM_ERROR, { value: true });
|
|
220
|
+
}
|
|
221
|
+
function isPlatformError(value) {
|
|
222
|
+
return typeof value === "object" && value !== null && PLATFORM_ERROR in value;
|
|
223
|
+
}
|
|
224
|
+
var EMPTY_RESPONSE = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/EmptyResponse");
|
|
225
|
+
function markEmptyResponse(error) {
|
|
226
|
+
Object.defineProperty(error, EMPTY_RESPONSE, { value: true });
|
|
227
|
+
return error;
|
|
228
|
+
}
|
|
229
|
+
function isEmptyResponse(value) {
|
|
230
|
+
return typeof value === "object" && value !== null && EMPTY_RESPONSE in value;
|
|
231
|
+
}
|
|
232
|
+
var TRANSPORT_FAILURE = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/TransportFailure");
|
|
233
|
+
function markTransportFailure(error) {
|
|
234
|
+
if (typeof error === "object" && error !== null) {
|
|
235
|
+
try {
|
|
236
|
+
Object.defineProperty(error, TRANSPORT_FAILURE, { value: true });
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return error;
|
|
241
|
+
}
|
|
242
|
+
function isTransportFailure(value) {
|
|
243
|
+
return typeof value === "object" && value !== null && TRANSPORT_FAILURE in value;
|
|
244
|
+
}
|
|
245
|
+
function describe(init) {
|
|
246
|
+
const status = init.status ?? 0;
|
|
247
|
+
const where = [init.method, init.url].filter(Boolean).join(" ");
|
|
248
|
+
const what = status > 0 ? `HTTP ${status}` : "request failed";
|
|
249
|
+
const head = where ? `${where} \u2192 ${what}` : what;
|
|
250
|
+
const code = init.code ? ` (${init.code})` : "";
|
|
251
|
+
return init.detail ? `${head}${code}: ${init.detail}` : `${head}${code}`;
|
|
252
|
+
}
|
|
253
|
+
var AdminError = class extends Error {
|
|
254
|
+
/** HTTP status. `0` means the request never produced one. */
|
|
255
|
+
status;
|
|
256
|
+
/** Machine-readable code from the response body, when it carried one. */
|
|
257
|
+
code;
|
|
258
|
+
/** Parsed response body, when there was one. */
|
|
259
|
+
body;
|
|
260
|
+
/** Requested URL, when known. */
|
|
261
|
+
url;
|
|
262
|
+
/** Request method, when known. */
|
|
263
|
+
method;
|
|
264
|
+
/**
|
|
265
|
+
* Text the failing side supplied — the only candidate for user-facing
|
|
266
|
+
* output. `undefined` when nothing was supplied.
|
|
267
|
+
*/
|
|
268
|
+
detail;
|
|
269
|
+
/** The request completed but the answer was unusable. See `AdminErrorInit`. */
|
|
270
|
+
emptyResponse;
|
|
271
|
+
/** The request never reached the server. See `AdminErrorInit`. */
|
|
272
|
+
transportFailure;
|
|
273
|
+
constructor(init = {}) {
|
|
274
|
+
super(init.message ?? describe(init), { cause: init.cause });
|
|
275
|
+
this.name = "AdminError";
|
|
276
|
+
this.status = init.status ?? 0;
|
|
277
|
+
this.code = init.code;
|
|
278
|
+
this.body = init.body;
|
|
279
|
+
this.url = init.url;
|
|
280
|
+
this.method = init.method;
|
|
281
|
+
this.detail = init.detail;
|
|
282
|
+
this.emptyResponse = init.emptyResponse ?? false;
|
|
283
|
+
this.transportFailure = init.transportFailure ?? false;
|
|
284
|
+
Object.defineProperty(this, ADMIN_ERROR, { value: true });
|
|
285
|
+
markPlatformError(this);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
function isAdminError(err) {
|
|
289
|
+
return typeof err === "object" && err !== null && ADMIN_ERROR in err;
|
|
290
|
+
}
|
|
291
|
+
function asRecord(value) {
|
|
292
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
293
|
+
}
|
|
294
|
+
function asString(value) {
|
|
295
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
296
|
+
}
|
|
297
|
+
function isAxiosNoResponseError(value) {
|
|
298
|
+
const record = asRecord(value);
|
|
299
|
+
if (!record) return false;
|
|
300
|
+
return record.isAxiosError === true && record.request != null && record.response == null;
|
|
301
|
+
}
|
|
302
|
+
function readErrorDetail(body) {
|
|
303
|
+
const record = asRecord(body);
|
|
304
|
+
if (!record) return asString(body);
|
|
305
|
+
const message = record.message;
|
|
306
|
+
if (Array.isArray(message)) {
|
|
307
|
+
const parts = message.filter((part) => typeof part === "string");
|
|
308
|
+
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
309
|
+
}
|
|
310
|
+
return asString(message) ?? asString(record.error) ?? asString(record.reason);
|
|
311
|
+
}
|
|
312
|
+
function readErrorCode(body) {
|
|
313
|
+
return asString(asRecord(body)?.code);
|
|
314
|
+
}
|
|
315
|
+
function toAdminError(err) {
|
|
316
|
+
if (isAdminError(err)) return err;
|
|
317
|
+
const record = asRecord(err);
|
|
318
|
+
const response = asRecord(record?.response);
|
|
319
|
+
if (response && typeof response.status === "number") {
|
|
320
|
+
const config = asRecord(record?.config);
|
|
321
|
+
const body = response.data;
|
|
322
|
+
return new AdminError({
|
|
323
|
+
status: response.status,
|
|
324
|
+
code: readErrorCode(body),
|
|
325
|
+
body,
|
|
326
|
+
url: asString(config?.url),
|
|
327
|
+
method: asString(config?.method)?.toUpperCase(),
|
|
328
|
+
// Only what the response actually said. `err.message` here is
|
|
329
|
+
// axios's own generated line ("Request failed with status code
|
|
330
|
+
// 401") — English, untranslated, and about the transport rather
|
|
331
|
+
// than about what went wrong. Letting it through as `detail` would
|
|
332
|
+
// make `adminErrorMessage` return it in preference to the
|
|
333
|
+
// localized wording, which is the whole point of that wording.
|
|
334
|
+
detail: readErrorDetail(body),
|
|
335
|
+
cause: err
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
if (record && typeof record.status === "number") {
|
|
339
|
+
const ours = isPlatformError(err);
|
|
340
|
+
return new AdminError({
|
|
341
|
+
status: record.status,
|
|
342
|
+
code: readErrorCode(record.body) ?? asString(record.code),
|
|
343
|
+
body: record.body,
|
|
344
|
+
// Same reasoning as above: `BundlesApiError`'s message is
|
|
345
|
+
// "Bundles API responded with HTTP 403", a diagnostic. It stays on
|
|
346
|
+
// `message`, where logs read it.
|
|
347
|
+
detail: readErrorDetail(record.body) ?? (ours ? void 0 : asString(record.message)),
|
|
348
|
+
// Asked of the throw site, not of the class: only the site that
|
|
349
|
+
// read the response knows whether one arrived. `status === 0 &&
|
|
350
|
+
// ours` was the same question put to the wrong witness — the
|
|
351
|
+
// loaders raise branded errors from a status the client chose, so
|
|
352
|
+
// a client that resolves a transport failure as `0` turned a
|
|
353
|
+
// failed GET into "the change may have been applied".
|
|
354
|
+
emptyResponse: isEmptyResponse(err),
|
|
355
|
+
transportFailure: isTransportFailure(err),
|
|
356
|
+
message: asString(record.message),
|
|
357
|
+
cause: err
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
if (err instanceof Error) {
|
|
361
|
+
const transport = isTransportFailure(err) || isAxiosNoResponseError(err);
|
|
362
|
+
const saidSomethingReadable = !transport && !(err instanceof TypeError);
|
|
363
|
+
return new AdminError({
|
|
364
|
+
detail: saidSomethingReadable ? asString(err.message) : void 0,
|
|
365
|
+
transportFailure: transport,
|
|
366
|
+
message: err.message,
|
|
367
|
+
cause: err
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
const text = asString(err) ?? asString(record?.message);
|
|
371
|
+
return new AdminError({
|
|
372
|
+
code: asString(record?.code),
|
|
373
|
+
detail: text,
|
|
374
|
+
message: text,
|
|
375
|
+
cause: err
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function statusKey(status) {
|
|
379
|
+
if (status === 400 || status === 422) return "validation";
|
|
380
|
+
if (status === 401) return "unauthorized";
|
|
381
|
+
if (status === 403) return "forbidden";
|
|
382
|
+
if (status === 404) return "notFound";
|
|
383
|
+
if (status === 409) return "conflict";
|
|
384
|
+
if (status === 429) return "rateLimited";
|
|
385
|
+
if (status >= 500) return "server";
|
|
386
|
+
return void 0;
|
|
387
|
+
}
|
|
388
|
+
function adminErrorMessage(err, msgs) {
|
|
389
|
+
const error = toAdminError(err);
|
|
390
|
+
if (error.detail) return error.detail;
|
|
391
|
+
if (error.emptyResponse) return msgs.emptyResponse;
|
|
392
|
+
if (error.transportFailure) return msgs.network;
|
|
393
|
+
if (error.status > 0) {
|
|
394
|
+
const key = statusKey(error.status);
|
|
395
|
+
return key ? msgs[key] : formatMessage(msgs.httpStatus, { status: error.status });
|
|
396
|
+
}
|
|
397
|
+
return msgs.unexpected;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/client/http-json.ts
|
|
401
|
+
function serverAnswered(status) {
|
|
402
|
+
return status > 0;
|
|
403
|
+
}
|
|
404
|
+
function requireServerAnswer(status, method, url, raise) {
|
|
405
|
+
if (serverAnswered(status)) return;
|
|
406
|
+
throw markTransportFailure(
|
|
407
|
+
raise(`${method} ${url} produced no HTTP status \u2014 the request did not reach the server`)
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
var HttpJsonError = AdminError;
|
|
411
|
+
async function readErrorBody(res) {
|
|
412
|
+
try {
|
|
413
|
+
return await res.json();
|
|
414
|
+
} catch {
|
|
415
|
+
return void 0;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
async function failed(res, method, url) {
|
|
419
|
+
const body = await readErrorBody(res);
|
|
420
|
+
return new AdminError({
|
|
421
|
+
status: res.status,
|
|
422
|
+
// Read here rather than left to the status: `status: 0` reaches
|
|
423
|
+
// `adminErrorMessage` as "nothing is known about this failure", and
|
|
424
|
+
// this seam knows better — it held the response.
|
|
425
|
+
transportFailure: !serverAnswered(res.status),
|
|
426
|
+
code: readErrorCode(body),
|
|
427
|
+
body,
|
|
428
|
+
url,
|
|
429
|
+
method,
|
|
430
|
+
// Shared with `toAdminError` on purpose: a NestJS `ValidationPipe`
|
|
431
|
+
// rejection carries `message` as an array, and reading it here in a
|
|
432
|
+
// second, narrower way is how those constraints went missing.
|
|
433
|
+
detail: readErrorDetail(body)
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
function trimTrailingSlashes(url) {
|
|
437
|
+
let end = url.length;
|
|
438
|
+
while (end > 0 && url[end - 1] === "/") end--;
|
|
439
|
+
return url.slice(0, end);
|
|
440
|
+
}
|
|
441
|
+
async function getJson(http, url) {
|
|
442
|
+
const res = await http(url);
|
|
443
|
+
if (res.status < 200 || res.status >= 300) {
|
|
444
|
+
throw await failed(res, "GET", url);
|
|
445
|
+
}
|
|
446
|
+
return await res.json();
|
|
447
|
+
}
|
|
448
|
+
async function postJson(http, url, body) {
|
|
449
|
+
const res = await http(url, {
|
|
450
|
+
method: "POST",
|
|
451
|
+
headers: { "Content-Type": "application/json" },
|
|
452
|
+
body: JSON.stringify(body)
|
|
453
|
+
});
|
|
454
|
+
if (res.status < 200 || res.status >= 300) {
|
|
455
|
+
throw await failed(res, "POST", url);
|
|
456
|
+
}
|
|
457
|
+
return await res.json();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/client/http/fetch-http-client.ts
|
|
461
|
+
var ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:|^\/\//i;
|
|
462
|
+
function resolveUrl(url, baseUrl) {
|
|
463
|
+
if (!baseUrl || ABSOLUTE_URL.test(url)) return url;
|
|
464
|
+
return `${trimTrailingSlashes(baseUrl)}${url.startsWith("/") ? "" : "/"}${url}`;
|
|
465
|
+
}
|
|
466
|
+
function createFetchHttpClient(options = {}) {
|
|
467
|
+
return async (url, init) => {
|
|
468
|
+
const headers = new Headers(await options.headers?.());
|
|
469
|
+
if (!headers.has("Accept")) headers.set("Accept", "application/json");
|
|
470
|
+
for (const [name, value] of Object.entries(init?.headers ?? {})) {
|
|
471
|
+
headers.set(name, value);
|
|
472
|
+
}
|
|
473
|
+
if (init?.body !== void 0 && !headers.has("Content-Type")) {
|
|
474
|
+
headers.set("Content-Type", "application/json");
|
|
475
|
+
}
|
|
476
|
+
let response;
|
|
477
|
+
try {
|
|
478
|
+
response = await fetch(resolveUrl(url, options.baseUrl), {
|
|
479
|
+
method: init?.method ?? "GET",
|
|
480
|
+
headers,
|
|
481
|
+
body: init?.body
|
|
482
|
+
});
|
|
483
|
+
} catch (err) {
|
|
484
|
+
throw markTransportFailure(err);
|
|
485
|
+
}
|
|
486
|
+
return response;
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
163
490
|
// src/client/types.ts
|
|
164
491
|
var NOOP_KV_STORE = {
|
|
165
492
|
get: () => null,
|
|
@@ -201,58 +528,334 @@ function resolveLocalStorage() {
|
|
|
201
528
|
}
|
|
202
529
|
}
|
|
203
530
|
function defaultHttpClient() {
|
|
204
|
-
return (
|
|
531
|
+
return createFetchHttpClient();
|
|
205
532
|
}
|
|
206
533
|
|
|
207
|
-
// src/client/http-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
534
|
+
// src/client/http/axios-http-client.ts
|
|
535
|
+
function stripped(url, prefixes) {
|
|
536
|
+
for (const prefix of prefixes) {
|
|
537
|
+
const trimmed = trimTrailingSlashes(prefix);
|
|
538
|
+
if (!trimmed || !url.startsWith(trimmed)) continue;
|
|
539
|
+
const rest = url.slice(trimmed.length);
|
|
540
|
+
if (rest === "") return "/";
|
|
541
|
+
if (rest.startsWith("/")) return rest;
|
|
542
|
+
if (rest.startsWith("?") || rest.startsWith("#")) return `/${rest}`;
|
|
543
|
+
}
|
|
544
|
+
return url;
|
|
545
|
+
}
|
|
546
|
+
function headerReader(headers) {
|
|
547
|
+
const record = typeof headers === "object" && headers !== null ? headers : {};
|
|
548
|
+
const lowered = /* @__PURE__ */ new Map();
|
|
549
|
+
for (const [key, value] of Object.entries(record)) lowered.set(key.toLowerCase(), value);
|
|
550
|
+
return (name) => {
|
|
551
|
+
const value = lowered.get(name.toLowerCase());
|
|
552
|
+
return value == null ? null : String(value);
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function bodyIsRaw(response, declared) {
|
|
556
|
+
if (declared !== "auto") return declared === "raw";
|
|
557
|
+
if (response.data === "") return true;
|
|
558
|
+
const config = response.config;
|
|
559
|
+
if (config?.transformResponse === null) return true;
|
|
560
|
+
if (Array.isArray(config?.transformResponse) && config.transformResponse.length === 0) {
|
|
561
|
+
return true;
|
|
214
562
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
563
|
+
const responseType = config?.responseType;
|
|
564
|
+
if (responseType === "json") return false;
|
|
565
|
+
if (typeof responseType === "string" && responseType !== "") return true;
|
|
566
|
+
const transitional = config?.transitional;
|
|
567
|
+
return typeof transitional === "object" && transitional !== null && transitional.forcedJSONParsing === false;
|
|
568
|
+
}
|
|
569
|
+
async function readBody(data) {
|
|
570
|
+
if (typeof data === "string" || data === null || data === void 0) return data;
|
|
571
|
+
if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
|
|
572
|
+
if (ArrayBuffer.isView(data)) return new TextDecoder().decode(data);
|
|
573
|
+
const maybe = data;
|
|
574
|
+
if (typeof maybe.pipe === "function" || typeof maybe.getReader === "function") {
|
|
575
|
+
throw new TypeError(
|
|
576
|
+
"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."
|
|
577
|
+
);
|
|
224
578
|
}
|
|
579
|
+
if (typeof maybe.text === "function") return maybe.text.call(data);
|
|
580
|
+
return data;
|
|
225
581
|
}
|
|
226
|
-
function
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
582
|
+
function adapt(response, declared) {
|
|
583
|
+
return {
|
|
584
|
+
status: response.status,
|
|
585
|
+
headers: { get: headerReader(response.headers) },
|
|
586
|
+
json: async () => {
|
|
587
|
+
const data = await readBody(response.data);
|
|
588
|
+
if (typeof data !== "string") return data;
|
|
589
|
+
return bodyIsRaw({ ...response, data }, declared) ? JSON.parse(data) : data;
|
|
590
|
+
},
|
|
591
|
+
text: async () => {
|
|
592
|
+
const data = await readBody(response.data);
|
|
593
|
+
return typeof data === "string" ? data : JSON.stringify(data);
|
|
594
|
+
}
|
|
595
|
+
};
|
|
230
596
|
}
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
597
|
+
function createAxiosHttpClient(instance, options = {}) {
|
|
598
|
+
const prefixes = typeof options.stripPrefix === "string" ? [options.stripPrefix] : options.stripPrefix ?? [];
|
|
599
|
+
const responseBody = options.responseBody ?? "auto";
|
|
600
|
+
return async (url, init) => {
|
|
601
|
+
try {
|
|
602
|
+
return adapt(
|
|
603
|
+
await instance.request({
|
|
604
|
+
url: stripped(url, prefixes),
|
|
605
|
+
method: (init?.method ?? "GET").toUpperCase(),
|
|
606
|
+
headers: init?.headers,
|
|
607
|
+
data: init?.body
|
|
608
|
+
}),
|
|
609
|
+
responseBody
|
|
610
|
+
);
|
|
611
|
+
} catch (err) {
|
|
612
|
+
const response = err?.response;
|
|
613
|
+
if (response && typeof response.status === "number") {
|
|
614
|
+
return adapt(response, responseBody);
|
|
615
|
+
}
|
|
616
|
+
throw isAxiosNoResponseError(err) ? markTransportFailure(err) : err;
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/client/resources/define-resource.ts
|
|
622
|
+
function defineResource(key, ops, options = {}) {
|
|
623
|
+
return { key, ops, projectScoped: options.projectScoped ?? false };
|
|
624
|
+
}
|
|
625
|
+
function requireProjectKey(def, ctx) {
|
|
626
|
+
if (!def.projectScoped) return;
|
|
627
|
+
if (typeof ctx.projectKey === "string" && ctx.projectKey.trim() !== "") return;
|
|
628
|
+
throw new Error(
|
|
629
|
+
`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().`
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
function bindResource(def, http, ctx) {
|
|
633
|
+
const readContext = typeof ctx === "function" ? ctx : () => ctx;
|
|
634
|
+
if (def.projectScoped) requireProjectKey(def, readContext());
|
|
635
|
+
const bound = {};
|
|
636
|
+
for (const [name, op] of Object.entries(def.ops)) {
|
|
637
|
+
bound[name] = async (...args) => {
|
|
638
|
+
const context = readContext();
|
|
639
|
+
requireProjectKey(def, context);
|
|
640
|
+
return op(http, context, ...args);
|
|
641
|
+
};
|
|
235
642
|
}
|
|
236
|
-
return
|
|
643
|
+
return bound;
|
|
237
644
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
645
|
+
|
|
646
|
+
// src/client/resources/resource-request.ts
|
|
647
|
+
async function requestJson(http, url, init = {}) {
|
|
648
|
+
const method = init.method ?? "GET";
|
|
649
|
+
const response = await http(url, {
|
|
650
|
+
method,
|
|
651
|
+
headers: { "content-type": "application/json", ...init.headers },
|
|
652
|
+
body: init.body === void 0 ? void 0 : JSON.stringify(init.body)
|
|
243
653
|
});
|
|
244
|
-
|
|
245
|
-
|
|
654
|
+
requireServerAnswer(
|
|
655
|
+
response.status,
|
|
656
|
+
method,
|
|
657
|
+
url,
|
|
658
|
+
(diagnostic) => new AdminError({ status: response.status, url, method, message: diagnostic })
|
|
659
|
+
);
|
|
660
|
+
if (response.status === 204) return null;
|
|
661
|
+
const body = await response.json().catch(() => null);
|
|
662
|
+
if (response.status >= 400) {
|
|
663
|
+
throw new AdminError({
|
|
664
|
+
status: response.status,
|
|
665
|
+
code: readErrorCode(body),
|
|
666
|
+
body,
|
|
667
|
+
// The third place this had to be read, and the second time it was
|
|
668
|
+
// read a narrower way: without `detail` a page migrating onto these
|
|
669
|
+
// resources loses "Plan already exists" and shows the generic
|
|
670
|
+
// status sentence instead. One decision, one function.
|
|
671
|
+
detail: readErrorDetail(body),
|
|
672
|
+
url,
|
|
673
|
+
method
|
|
674
|
+
});
|
|
246
675
|
}
|
|
247
|
-
return
|
|
676
|
+
return body;
|
|
677
|
+
}
|
|
678
|
+
async function requestJsonBody(http, url, what, init = {}) {
|
|
679
|
+
const body = await requestJson(http, url, init);
|
|
680
|
+
if (body === null || body === void 0) {
|
|
681
|
+
throw new AdminError({
|
|
682
|
+
status: 0,
|
|
683
|
+
url,
|
|
684
|
+
method: init.method ?? "GET",
|
|
685
|
+
// `what` is a diagnostic — "Create returned no body" — so it goes
|
|
686
|
+
// on `message`, not `detail`. As `detail` it out-ranked the
|
|
687
|
+
// localized wording and the operator read an English internal
|
|
688
|
+
// string instead of being told the change may already have landed.
|
|
689
|
+
message: what,
|
|
690
|
+
emptyResponse: true
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return body;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/client/resources/list-resource.ts
|
|
697
|
+
var LIST_FIRST_PAGE = 1;
|
|
698
|
+
var LIST_PAGE_SIZE_DEFAULT = 50;
|
|
699
|
+
var LIST_PAGE_SIZE_MAX = 200;
|
|
700
|
+
var LIST_PAGINATION_PARAMS = ["page", "pageSize"];
|
|
701
|
+
function isSentInQuery(value) {
|
|
702
|
+
return value !== void 0 && value !== null && value !== "";
|
|
703
|
+
}
|
|
704
|
+
function appendFilter(params, filter) {
|
|
705
|
+
for (const [key, value] of Object.entries(filter ?? {})) {
|
|
706
|
+
if (!isSentInQuery(value)) continue;
|
|
707
|
+
params.set(key, String(value));
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function filterQueryString(filter) {
|
|
711
|
+
const params = new URLSearchParams();
|
|
712
|
+
appendFilter(params, filter);
|
|
713
|
+
const query = params.toString();
|
|
714
|
+
return query ? `?${query}` : "";
|
|
715
|
+
}
|
|
716
|
+
function listUrl(endpoint, query = {}) {
|
|
717
|
+
const params = new URLSearchParams();
|
|
718
|
+
params.set("page", String(query.page ?? LIST_FIRST_PAGE));
|
|
719
|
+
params.set("pageSize", String(query.pageSize ?? LIST_PAGE_SIZE_DEFAULT));
|
|
720
|
+
appendFilter(params, query.filter);
|
|
721
|
+
return `${endpoint}${endpoint.includes("?") ? "&" : "?"}${params.toString()}`;
|
|
722
|
+
}
|
|
723
|
+
function readListPage(raw) {
|
|
724
|
+
if (Array.isArray(raw)) return { items: raw, total: raw.length };
|
|
725
|
+
if (raw === null || typeof raw !== "object") return { items: [], total: 0 };
|
|
726
|
+
const body = raw;
|
|
727
|
+
const page = {
|
|
728
|
+
items: Array.isArray(body.items) ? body.items : []
|
|
729
|
+
};
|
|
730
|
+
if (typeof body.total === "number") page.total = body.total;
|
|
731
|
+
if (typeof body.page === "number") page.page = body.page;
|
|
732
|
+
if (typeof body.pageSize === "number") page.pageSize = body.pageSize;
|
|
733
|
+
return page;
|
|
734
|
+
}
|
|
735
|
+
function clampListPage(page) {
|
|
736
|
+
return Math.max(LIST_FIRST_PAGE, Math.floor(page));
|
|
737
|
+
}
|
|
738
|
+
function clampListPageSize(size) {
|
|
739
|
+
return Math.min(LIST_PAGE_SIZE_MAX, Math.max(1, Math.floor(size)));
|
|
740
|
+
}
|
|
741
|
+
function defineListOp(endpoint) {
|
|
742
|
+
return async (http, ctx, query) => readListPage(await requestJson(http, listUrl(endpoint(ctx), query)));
|
|
248
743
|
}
|
|
249
744
|
|
|
745
|
+
// src/client/resources/plans.resource.ts
|
|
746
|
+
function plansUrl(ctx) {
|
|
747
|
+
return `${ctx.apiBase}/catalog/plans`;
|
|
748
|
+
}
|
|
749
|
+
function versionsUrl(ctx) {
|
|
750
|
+
return `${ctx.apiBase}/catalog/plan-versions`;
|
|
751
|
+
}
|
|
752
|
+
function scoped(url, ctx) {
|
|
753
|
+
return `${url}?projectKey=${encodeURIComponent(ctx.projectKey)}`;
|
|
754
|
+
}
|
|
755
|
+
var plansResource = defineResource(
|
|
756
|
+
"plans",
|
|
757
|
+
{
|
|
758
|
+
list: async (http, ctx) => await requestJson(http, scoped(plansUrl(ctx), ctx)) ?? [],
|
|
759
|
+
tenantCounts: async (http, ctx) => await requestJson(
|
|
760
|
+
http,
|
|
761
|
+
scoped(`${plansUrl(ctx)}/tenant-counts`, ctx)
|
|
762
|
+
) ?? {},
|
|
763
|
+
/**
|
|
764
|
+
* The project comes from the context, not from the caller.
|
|
765
|
+
*
|
|
766
|
+
* The endpoint takes it in the body, and the composable this replaces
|
|
767
|
+
* made that the caller's job — so a page had to keep hold of the
|
|
768
|
+
* project it was administering just to create in it, and `list()` and
|
|
769
|
+
* `create()` read two different sources for one value. A stale one
|
|
770
|
+
* creates a plan outside the project the list is showing.
|
|
771
|
+
*/
|
|
772
|
+
create: (http, ctx, data) => requestJsonBody(http, plansUrl(ctx), "Create returned no body", {
|
|
773
|
+
method: "POST",
|
|
774
|
+
body: { ...data, projectKey: ctx.projectKey }
|
|
775
|
+
}),
|
|
776
|
+
update: (http, ctx, planId, data) => requestJsonBody(
|
|
777
|
+
http,
|
|
778
|
+
`${plansUrl(ctx)}/${planId}`,
|
|
779
|
+
"Update returned no body",
|
|
780
|
+
{
|
|
781
|
+
method: "PATCH",
|
|
782
|
+
body: data
|
|
783
|
+
}
|
|
784
|
+
),
|
|
785
|
+
/** Marks the plan deleted. Fails while it still has versions. */
|
|
786
|
+
softDelete: async (http, ctx, planId) => {
|
|
787
|
+
await requestJson(http, `${plansUrl(ctx)}/${planId}`, { method: "DELETE" });
|
|
788
|
+
},
|
|
789
|
+
/** Removes the plan for good. 422 `PLAN_HAS_VERSIONS` when it cannot. */
|
|
790
|
+
hardDelete: async (http, ctx, planId) => {
|
|
791
|
+
await requestJson(http, `${plansUrl(ctx)}/${planId}/purge`, { method: "DELETE" });
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
{ projectScoped: true }
|
|
795
|
+
);
|
|
796
|
+
var planVersionsResource = defineResource("planVersions", {
|
|
797
|
+
listForPlan: async (http, ctx, planId) => await requestJson(http, `${plansUrl(ctx)}/${planId}/versions`) ?? [],
|
|
798
|
+
createDraft: (http, ctx, planId, data) => requestJsonBody(
|
|
799
|
+
http,
|
|
800
|
+
`${plansUrl(ctx)}/${planId}/versions`,
|
|
801
|
+
"CreateDraft returned no body",
|
|
802
|
+
{ method: "POST", body: data }
|
|
803
|
+
),
|
|
804
|
+
updateDraft: (http, ctx, versionId, data) => requestJsonBody(
|
|
805
|
+
http,
|
|
806
|
+
`${versionsUrl(ctx)}/${versionId}`,
|
|
807
|
+
"UpdateDraft returned no body",
|
|
808
|
+
{ method: "PATCH", body: data }
|
|
809
|
+
),
|
|
810
|
+
publish: (http, ctx, versionId, options = {}) => requestJsonBody(
|
|
811
|
+
http,
|
|
812
|
+
`${versionsUrl(ctx)}/${versionId}/publish`,
|
|
813
|
+
"Publish returned no body",
|
|
814
|
+
{ method: "POST", body: options }
|
|
815
|
+
),
|
|
816
|
+
discardDraft: async (http, ctx, versionId) => {
|
|
817
|
+
await requestJson(http, `${versionsUrl(ctx)}/${versionId}`, { method: "DELETE" });
|
|
818
|
+
},
|
|
819
|
+
/**
|
|
820
|
+
* Ends a live version on `endsAt`, with no successor. Idempotent — a
|
|
821
|
+
* second call with another date overwrites the first.
|
|
822
|
+
*
|
|
823
|
+
* `endsAt` is the date itself, not a payload: the endpoint's body is
|
|
824
|
+
* `{ endsAt }` and wrapping it is this operation's job, not the caller's.
|
|
825
|
+
*/
|
|
826
|
+
terminate: (http, ctx, versionId, endsAt) => requestJsonBody(
|
|
827
|
+
http,
|
|
828
|
+
`${versionsUrl(ctx)}/${versionId}/terminate`,
|
|
829
|
+
"Terminate returned no body",
|
|
830
|
+
{ method: "POST", body: { endsAt } }
|
|
831
|
+
)
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// src/client/resources/tenants.resource.ts
|
|
835
|
+
var tenantsResource = defineResource("tenants", {
|
|
836
|
+
list: defineListOp((ctx) => `${ctx.apiBase}/tenants`)
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
// src/client/resources/audit.resource.ts
|
|
840
|
+
var auditResource = defineResource("audit", {
|
|
841
|
+
list: defineListOp((ctx) => `${ctx.apiBase}/audit`)
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
// src/client/resources/index.ts
|
|
845
|
+
var platformResources = {
|
|
846
|
+
plans: plansResource,
|
|
847
|
+
planVersions: planVersionsResource,
|
|
848
|
+
tenants: tenantsResource,
|
|
849
|
+
audit: auditResource
|
|
850
|
+
};
|
|
851
|
+
|
|
250
852
|
// src/client/boot-loader.ts
|
|
251
853
|
var BootLoadError = class extends Error {
|
|
252
854
|
constructor(status, message) {
|
|
253
855
|
super(message);
|
|
254
856
|
this.status = status;
|
|
255
857
|
this.name = "BootLoadError";
|
|
858
|
+
markPlatformError(this);
|
|
256
859
|
}
|
|
257
860
|
status;
|
|
258
861
|
};
|
|
@@ -270,19 +873,27 @@ var BootLoader = class {
|
|
|
270
873
|
}
|
|
271
874
|
async load() {
|
|
272
875
|
const res = await this.http(this.endpoint);
|
|
876
|
+
requireServerAnswer(
|
|
877
|
+
res.status,
|
|
878
|
+
"GET",
|
|
879
|
+
this.endpoint,
|
|
880
|
+
(diagnostic) => new BootLoadError(res.status, diagnostic)
|
|
881
|
+
);
|
|
273
882
|
if (res.status !== 200) {
|
|
274
|
-
throw new BootLoadError(res.status, `Boot
|
|
883
|
+
throw new BootLoadError(res.status, `Boot endpoint responded with HTTP ${res.status}`);
|
|
275
884
|
}
|
|
276
885
|
return await res.json();
|
|
277
886
|
}
|
|
278
887
|
};
|
|
279
888
|
|
|
280
889
|
// src/client/manifest-loader.ts
|
|
890
|
+
var NOT_MODIFIED = 304;
|
|
281
891
|
var ManifestLoadError = class extends Error {
|
|
282
892
|
constructor(status, message) {
|
|
283
893
|
super(message);
|
|
284
894
|
this.status = status;
|
|
285
895
|
this.name = "ManifestLoadError";
|
|
896
|
+
markPlatformError(this);
|
|
286
897
|
}
|
|
287
898
|
status;
|
|
288
899
|
};
|
|
@@ -310,24 +921,48 @@ var ManifestLoader = class {
|
|
|
310
921
|
/**
|
|
311
922
|
* Loads the current manifest. On a cache hit (304) the cached
|
|
312
923
|
* body is returned — otherwise the fresh server body.
|
|
924
|
+
*
|
|
925
|
+
* A 304 with no usable cached body is not a failure to report but one to
|
|
926
|
+
* undo: the request only carried `If-None-Match` because this loader put
|
|
927
|
+
* the ETag there, and the cache it validated against is this loader's own.
|
|
928
|
+
* So it drops the pair and asks once more without the header, which is the
|
|
929
|
+
* same recovery a person would have had to trigger by hand — except that
|
|
930
|
+
* the person reading an admin screen has neither this loader nor a
|
|
931
|
+
* console. Exactly one retry: a 304 to a request that asked nothing
|
|
932
|
+
* conditional is a server fault, and no repetition fixes it.
|
|
313
933
|
*/
|
|
314
934
|
async load() {
|
|
315
935
|
const cachedEtag = this.storage.get(this.etagKey);
|
|
936
|
+
const res = await this.request(cachedEtag);
|
|
937
|
+
if (res.status !== NOT_MODIFIED) return this.acceptFresh(res);
|
|
938
|
+
const cached = this.readCachedBody();
|
|
939
|
+
if (cached) return cached.body;
|
|
940
|
+
if (cachedEtag) {
|
|
941
|
+
this.clearCache();
|
|
942
|
+
const fresh = await this.request(null);
|
|
943
|
+
if (fresh.status !== NOT_MODIFIED) return this.acceptFresh(fresh);
|
|
944
|
+
}
|
|
945
|
+
throw new ManifestLoadError(
|
|
946
|
+
NOT_MODIFIED,
|
|
947
|
+
"Manifest endpoint answered HTTP 304 to a request without If-None-Match"
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
/** Sends the manifest GET, conditional when an ETag is supplied. */
|
|
951
|
+
request(etag) {
|
|
316
952
|
const headers = {};
|
|
317
953
|
const token = this.getAuthToken?.();
|
|
318
954
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
955
|
+
if (etag) headers["If-None-Match"] = etag;
|
|
956
|
+
return this.http(this.endpoint, { method: "GET", headers });
|
|
957
|
+
}
|
|
958
|
+
/** Takes a non-304 response as the current manifest and caches it. */
|
|
959
|
+
async acceptFresh(res) {
|
|
960
|
+
requireServerAnswer(
|
|
961
|
+
res.status,
|
|
962
|
+
"GET",
|
|
963
|
+
this.endpoint,
|
|
964
|
+
(diagnostic) => new ManifestLoadError(res.status, diagnostic)
|
|
965
|
+
);
|
|
331
966
|
if (res.status !== 200) {
|
|
332
967
|
throw new ManifestLoadError(
|
|
333
968
|
res.status,
|
|
@@ -662,7 +1297,7 @@ var ActionRegistry = class {
|
|
|
662
1297
|
// src/client/batch-column-fetcher.ts
|
|
663
1298
|
var BatchColumnDriftError = class extends Error {
|
|
664
1299
|
constructor(column, reason) {
|
|
665
|
-
super(`
|
|
1300
|
+
super(`Column "${column.key}": ${reason}`);
|
|
666
1301
|
this.column = column;
|
|
667
1302
|
this.name = "BatchColumnDriftError";
|
|
668
1303
|
}
|
|
@@ -705,7 +1340,7 @@ var BatchColumnFetcher = class {
|
|
|
705
1340
|
const res = await this.http(url, { method: "GET", headers });
|
|
706
1341
|
if (res.status !== 200) {
|
|
707
1342
|
throw new Error(
|
|
708
|
-
`
|
|
1343
|
+
`Column "${column.key}" \u2014 endpoint ${column.endpoint} responded with HTTP ${res.status}`
|
|
709
1344
|
);
|
|
710
1345
|
}
|
|
711
1346
|
const body = await res.json();
|
|
@@ -781,10 +1416,10 @@ function createAdminResourceClient(options) {
|
|
|
781
1416
|
tenantsEndpoint,
|
|
782
1417
|
loadTenantDetail: (slug) => getJson2(`${tenantsEndpoint}/${encodeURIComponent(slug)}`),
|
|
783
1418
|
loadUsers: (filter) => getJson2(
|
|
784
|
-
`${base}/users${
|
|
1419
|
+
`${base}/users${filterQueryString({ q: filter.q, tenant: filter.tenant })}`
|
|
785
1420
|
),
|
|
786
1421
|
loadAudit: (filter) => getJson2(
|
|
787
|
-
`${base}/audit${
|
|
1422
|
+
`${base}/audit${filterQueryString({
|
|
788
1423
|
actor: filter.actor,
|
|
789
1424
|
action: filter.action,
|
|
790
1425
|
entity: filter.entity,
|
|
@@ -794,7 +1429,7 @@ function createAdminResourceClient(options) {
|
|
|
794
1429
|
),
|
|
795
1430
|
loadSubscriptions: () => getJson2(`${base}/subscriptions`),
|
|
796
1431
|
loadPromos: (filter) => getJson2(
|
|
797
|
-
`${base}/promo-codes${
|
|
1432
|
+
`${base}/promo-codes${filterQueryString({
|
|
798
1433
|
search: filter.search,
|
|
799
1434
|
status: filter.status
|
|
800
1435
|
})}`
|
|
@@ -819,21 +1454,6 @@ function assertSuccess(status, method, url) {
|
|
|
819
1454
|
throw new Error(`${method} ${url} \u2192 HTTP ${status}`);
|
|
820
1455
|
}
|
|
821
1456
|
}
|
|
822
|
-
function queryString(params) {
|
|
823
|
-
const search = new URLSearchParams();
|
|
824
|
-
for (const [key, value2] of Object.entries(params)) {
|
|
825
|
-
if (value2 === void 0 || value2 === null || value2 === "") continue;
|
|
826
|
-
search.set(key, String(value2));
|
|
827
|
-
}
|
|
828
|
-
const value = search.toString();
|
|
829
|
-
return value ? `?${value}` : "";
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
// src/client/i18n/format.ts
|
|
833
|
-
var import_types4 = require("@saasicat/types");
|
|
834
|
-
function formatMessage(template, params) {
|
|
835
|
-
return (0, import_types4.formatErrorMessage)(template, params);
|
|
836
|
-
}
|
|
837
1457
|
|
|
838
1458
|
// src/client/i18n/currency.ts
|
|
839
1459
|
var DEFAULT_CURRENCY = "EUR";
|
|
@@ -1532,9 +2152,6 @@ var discoveryMessages = defineMessages(
|
|
|
1532
2152
|
replacedBy: "ersetzt durch {key}",
|
|
1533
2153
|
replaces: "ersetzt: {keys}",
|
|
1534
2154
|
reapproveEmphasis: "erneut freigeben",
|
|
1535
|
-
errorDiscoveryHttp: "Discovery-Endpoint antwortete mit HTTP {status}",
|
|
1536
|
-
errorRescanHttp: "Discovery-Rescan antwortete mit HTTP {status}",
|
|
1537
|
-
errorCatalogHttp: "Catalog-Entries-API antwortete mit HTTP {status}",
|
|
1538
2155
|
capList: {
|
|
1539
2156
|
orphanFeature: "Feature im Katalog ohne implementierende Capability \u2014 blockiert im blocking-Strict-Mode das Plan-Publish.",
|
|
1540
2157
|
removedFromCode: "aus Code entfernt"
|
|
@@ -1636,9 +2253,6 @@ var discoveryMessages = defineMessages(
|
|
|
1636
2253
|
replacedBy: "replaced by {key}",
|
|
1637
2254
|
replaces: "replaces: {keys}",
|
|
1638
2255
|
reapproveEmphasis: "approve again",
|
|
1639
|
-
errorDiscoveryHttp: "Discovery endpoint responded with HTTP {status}",
|
|
1640
|
-
errorRescanHttp: "Discovery rescan responded with HTTP {status}",
|
|
1641
|
-
errorCatalogHttp: "Catalog entries API responded with HTTP {status}",
|
|
1642
2256
|
capList: {
|
|
1643
2257
|
orphanFeature: "Feature in the catalog without an implementing capability \u2014 blocks plan publishing in blocking strict mode.",
|
|
1644
2258
|
removedFromCode: "removed from code"
|
|
@@ -1843,6 +2457,36 @@ var emailMessages = defineMessages(
|
|
|
1843
2457
|
}
|
|
1844
2458
|
);
|
|
1845
2459
|
|
|
2460
|
+
// src/client/i18n/messages/errors.ts
|
|
2461
|
+
var errorsMessages = defineMessages(
|
|
2462
|
+
{
|
|
2463
|
+
network: "Die Anfrage konnte nicht abgeschlossen werden. Bitte Verbindung pr\xFCfen.",
|
|
2464
|
+
emptyResponse: "Der Server hat nichts zur\xFCckgegeben. Bitte pr\xFCfen, ob die \xC4nderung angekommen ist.",
|
|
2465
|
+
unauthorized: "Die Sitzung ist abgelaufen. Bitte erneut anmelden.",
|
|
2466
|
+
forbidden: "F\xFCr diese Aktion fehlt die Berechtigung.",
|
|
2467
|
+
notFound: "Der angeforderte Eintrag existiert nicht mehr.",
|
|
2468
|
+
conflict: "Der Eintrag wurde zwischenzeitlich ge\xE4ndert. Bitte neu laden.",
|
|
2469
|
+
validation: "Die Eingaben wurden nicht akzeptiert.",
|
|
2470
|
+
rateLimited: "Zu viele Anfragen. Bitte kurz warten und erneut versuchen.",
|
|
2471
|
+
server: "Der Server konnte die Anfrage nicht verarbeiten.",
|
|
2472
|
+
httpStatus: "Die Anfrage ist fehlgeschlagen (HTTP {status}).",
|
|
2473
|
+
unexpected: "Es ist ein unerwarteter Fehler aufgetreten. Bitte erneut versuchen."
|
|
2474
|
+
},
|
|
2475
|
+
{
|
|
2476
|
+
network: "The request could not be completed. Please check your connection.",
|
|
2477
|
+
emptyResponse: "The server returned nothing. Please check whether the change was applied.",
|
|
2478
|
+
unauthorized: "Your session has expired. Please sign in again.",
|
|
2479
|
+
forbidden: "You do not have permission for this action.",
|
|
2480
|
+
notFound: "The requested entry no longer exists.",
|
|
2481
|
+
conflict: "The entry changed in the meantime. Please reload.",
|
|
2482
|
+
validation: "The input was not accepted.",
|
|
2483
|
+
rateLimited: "Too many requests. Please wait a moment and try again.",
|
|
2484
|
+
server: "The server could not process the request.",
|
|
2485
|
+
httpStatus: "The request failed (HTTP {status}).",
|
|
2486
|
+
unexpected: "Something went wrong. Please try again."
|
|
2487
|
+
}
|
|
2488
|
+
);
|
|
2489
|
+
|
|
1846
2490
|
// src/client/i18n/messages/marketing.ts
|
|
1847
2491
|
var marketingMessages = defineMessages(
|
|
1848
2492
|
{
|
|
@@ -1967,9 +2611,6 @@ var marketingMessages = defineMessages(
|
|
|
1967
2611
|
colorLabel: "Farbe",
|
|
1968
2612
|
delete: "Aktion l\xF6schen",
|
|
1969
2613
|
deleteConfirm: "Aktion \u201E{label}\u201C wirklich l\xF6schen?"
|
|
1970
|
-
},
|
|
1971
|
-
errors: {
|
|
1972
|
-
projectionsApi: "MarketingProjections-API antwortete mit HTTP {status}"
|
|
1973
2614
|
}
|
|
1974
2615
|
},
|
|
1975
2616
|
{
|
|
@@ -2094,9 +2735,6 @@ var marketingMessages = defineMessages(
|
|
|
2094
2735
|
colorLabel: "Color",
|
|
2095
2736
|
delete: "Delete promotion",
|
|
2096
2737
|
deleteConfirm: 'Really delete promotion "{label}"?'
|
|
2097
|
-
},
|
|
2098
|
-
errors: {
|
|
2099
|
-
projectionsApi: "MarketingProjections API responded with HTTP {status}"
|
|
2100
2738
|
}
|
|
2101
2739
|
}
|
|
2102
2740
|
);
|
|
@@ -3290,7 +3928,6 @@ var promosMessages = defineMessages(
|
|
|
3290
3928
|
{
|
|
3291
3929
|
title: "Promo-Codes",
|
|
3292
3930
|
createAction: "Promo-Code anlegen",
|
|
3293
|
-
apiErrorHttpStatus: "Promotions-API antwortete mit HTTP {status}",
|
|
3294
3931
|
list: {
|
|
3295
3932
|
searchLabel: "Suche (Code)",
|
|
3296
3933
|
tileScheduled: "Geplant",
|
|
@@ -3388,7 +4025,6 @@ var promosMessages = defineMessages(
|
|
|
3388
4025
|
{
|
|
3389
4026
|
title: "Promo codes",
|
|
3390
4027
|
createAction: "Create promo code",
|
|
3391
|
-
apiErrorHttpStatus: "Promotions API responded with HTTP {status}",
|
|
3392
4028
|
list: {
|
|
3393
4029
|
searchLabel: "Search (code)",
|
|
3394
4030
|
tileScheduled: "Scheduled",
|
|
@@ -3496,7 +4132,13 @@ var shellMessages = defineMessages(
|
|
|
3496
4132
|
subtitle: "SuperAdmin \xB7 Plattform-Verwaltung",
|
|
3497
4133
|
roleBadge: "SUPER ADMIN",
|
|
3498
4134
|
logout: "Abmelden",
|
|
3499
|
-
language: "Sprache"
|
|
4135
|
+
language: "Sprache",
|
|
4136
|
+
theme: {
|
|
4137
|
+
label: "Darstellung",
|
|
4138
|
+
light: "Hell",
|
|
4139
|
+
dark: "Dunkel",
|
|
4140
|
+
system: "System"
|
|
4141
|
+
}
|
|
3500
4142
|
},
|
|
3501
4143
|
drawer: {
|
|
3502
4144
|
docs: "Doku \xF6ffnen"
|
|
@@ -3565,7 +4207,13 @@ var shellMessages = defineMessages(
|
|
|
3565
4207
|
subtitle: "SuperAdmin \xB7 Platform administration",
|
|
3566
4208
|
roleBadge: "SUPER ADMIN",
|
|
3567
4209
|
logout: "Sign out",
|
|
3568
|
-
language: "Language"
|
|
4210
|
+
language: "Language",
|
|
4211
|
+
theme: {
|
|
4212
|
+
label: "Appearance",
|
|
4213
|
+
light: "Light",
|
|
4214
|
+
dark: "Dark",
|
|
4215
|
+
system: "System"
|
|
4216
|
+
}
|
|
3569
4217
|
},
|
|
3570
4218
|
drawer: {
|
|
3571
4219
|
docs: "Open documentation"
|
|
@@ -3809,6 +4457,7 @@ var usersMessages = defineMessages(
|
|
|
3809
4457
|
function catalogFor(locale) {
|
|
3810
4458
|
return {
|
|
3811
4459
|
common: commonMessages[locale],
|
|
4460
|
+
errors: errorsMessages[locale],
|
|
3812
4461
|
nav: navMessages[locale],
|
|
3813
4462
|
shell: shellMessages[locale],
|
|
3814
4463
|
dashboard: dashboardMessages[locale],
|
|
@@ -4063,6 +4712,11 @@ var SUPER_ADMIN_NOTIFY_KEY = /* @__PURE__ */ Symbol.for(
|
|
|
4063
4712
|
"@saasicat/ui-vue/SUPER_ADMIN_NOTIFY"
|
|
4064
4713
|
);
|
|
4065
4714
|
|
|
4715
|
+
// src/vue/ui-confirm.ts
|
|
4716
|
+
var SUPER_ADMIN_CONFIRM_KEY = /* @__PURE__ */ Symbol.for(
|
|
4717
|
+
"@saasicat/ui-vue/SUPER_ADMIN_CONFIRM"
|
|
4718
|
+
);
|
|
4719
|
+
|
|
4066
4720
|
// src/vue/use-super-admin-context.ts
|
|
4067
4721
|
var import_vue = require("vue");
|
|
4068
4722
|
function useSuperAdminExtensions() {
|
|
@@ -4153,10 +4807,10 @@ function useSaMessages(namespace) {
|
|
|
4153
4807
|
|
|
4154
4808
|
// src/vue/use-sa-theme.ts
|
|
4155
4809
|
var import_vue3 = require("vue");
|
|
4156
|
-
var
|
|
4157
|
-
var
|
|
4810
|
+
var SA_COLOR_SCHEMES = ["light", "dark", "system"];
|
|
4811
|
+
var SA_THEME_STORAGE_KEY = "saasicat.theme.scheme";
|
|
4158
4812
|
function isScheme(value) {
|
|
4159
|
-
return typeof value === "string" &&
|
|
4813
|
+
return typeof value === "string" && SA_COLOR_SCHEMES.includes(value);
|
|
4160
4814
|
}
|
|
4161
4815
|
function systemScheme() {
|
|
4162
4816
|
const value = (0, import_vue3.ref)("light");
|
|
@@ -4177,13 +4831,19 @@ function createSaTheme(options = {}) {
|
|
|
4177
4831
|
const persist = options.persist !== false;
|
|
4178
4832
|
const provided = options.scheme;
|
|
4179
4833
|
const appOwnsIt = (0, import_vue3.isRef)(provided);
|
|
4180
|
-
const
|
|
4834
|
+
const key = `${options.storageKeyPrefix ?? ""}${SA_THEME_STORAGE_KEY}`;
|
|
4835
|
+
const stored = persist && !appOwnsIt ? storage.get(key) : null;
|
|
4181
4836
|
const scheme = (0, import_vue3.isRef)(provided) ? provided : (0, import_vue3.ref)(isScheme(stored) ? stored : provided ?? "system");
|
|
4182
4837
|
const { value: system, unsubscribe } = systemScheme();
|
|
4183
|
-
const stopPersisting = persist && !appOwnsIt ? (0, import_vue3.watch)(scheme, (next) => storage.set(
|
|
4838
|
+
const stopPersisting = persist && !appOwnsIt ? (0, import_vue3.watch)(scheme, (next) => storage.set(key, next)) : null;
|
|
4184
4839
|
return {
|
|
4185
4840
|
scheme,
|
|
4186
4841
|
resolved: (0, import_vue3.computed)(() => scheme.value === "system" ? system.value : scheme.value),
|
|
4842
|
+
// A readonly ref (typically a `computed` derived from a profile
|
|
4843
|
+
// setting) silently swallows writes, so the switcher would be a dead
|
|
4844
|
+
// control. TypeScript ignores `readonly` when checking assignability,
|
|
4845
|
+
// so the guard has to be a runtime one — same as the locale next door.
|
|
4846
|
+
switcherEnabled: (options.switcher ?? true) && !(0, import_vue3.isReadonly)(scheme),
|
|
4187
4847
|
dispose: () => {
|
|
4188
4848
|
unsubscribe();
|
|
4189
4849
|
stopPersisting?.();
|
|
@@ -4295,22 +4955,17 @@ var import_vue5 = require("vue");
|
|
|
4295
4955
|
function useApiList(options) {
|
|
4296
4956
|
const http = options.http ?? defaultHttpClient();
|
|
4297
4957
|
const items = (0, import_vue5.ref)([]);
|
|
4298
|
-
const page = (0, import_vue5.ref)(
|
|
4299
|
-
const pageSize = (0, import_vue5.ref)(
|
|
4958
|
+
const page = (0, import_vue5.ref)(LIST_FIRST_PAGE);
|
|
4959
|
+
const pageSize = (0, import_vue5.ref)(LIST_PAGE_SIZE_DEFAULT);
|
|
4300
4960
|
const total = (0, import_vue5.ref)(0);
|
|
4301
4961
|
const loading = (0, import_vue5.ref)(false);
|
|
4302
4962
|
const error = (0, import_vue5.ref)(null);
|
|
4303
4963
|
function buildUrl() {
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
if (v === void 0 || v === null || v === "") continue;
|
|
4310
|
-
params.set(k, String(v));
|
|
4311
|
-
}
|
|
4312
|
-
const sep = options.endpoint.includes("?") ? "&" : "?";
|
|
4313
|
-
return `${options.endpoint}${sep}${params.toString()}`;
|
|
4964
|
+
return listUrl(options.endpoint, {
|
|
4965
|
+
page: page.value,
|
|
4966
|
+
pageSize: pageSize.value,
|
|
4967
|
+
filter: options.filter?.value
|
|
4968
|
+
});
|
|
4314
4969
|
}
|
|
4315
4970
|
async function load() {
|
|
4316
4971
|
loading.value = true;
|
|
@@ -4323,20 +4978,11 @@ function useApiList(options) {
|
|
|
4323
4978
|
if (res.status !== 200) {
|
|
4324
4979
|
throw new Error(`Endpoint ${options.endpoint} \u2192 HTTP ${res.status}`);
|
|
4325
4980
|
}
|
|
4326
|
-
const
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
const body = raw;
|
|
4332
|
-
items.value = body.items ?? [];
|
|
4333
|
-
if (typeof body.page === "number") page.value = body.page;
|
|
4334
|
-
if (typeof body.pageSize === "number") pageSize.value = body.pageSize;
|
|
4335
|
-
if (typeof body.total === "number") total.value = body.total;
|
|
4336
|
-
} else {
|
|
4337
|
-
items.value = [];
|
|
4338
|
-
total.value = 0;
|
|
4339
|
-
}
|
|
4981
|
+
const body = readListPage(await res.json());
|
|
4982
|
+
items.value = body.items;
|
|
4983
|
+
if (typeof body.page === "number") page.value = body.page;
|
|
4984
|
+
if (typeof body.pageSize === "number") pageSize.value = body.pageSize;
|
|
4985
|
+
if (typeof body.total === "number") total.value = body.total;
|
|
4340
4986
|
} catch (err) {
|
|
4341
4987
|
error.value = err instanceof Error ? err : new Error(String(err));
|
|
4342
4988
|
items.value = [];
|
|
@@ -4346,19 +4992,19 @@ function useApiList(options) {
|
|
|
4346
4992
|
}
|
|
4347
4993
|
}
|
|
4348
4994
|
async function goToPage(p) {
|
|
4349
|
-
page.value =
|
|
4995
|
+
page.value = clampListPage(p);
|
|
4350
4996
|
await load();
|
|
4351
4997
|
}
|
|
4352
4998
|
async function setPageSize(size) {
|
|
4353
|
-
pageSize.value =
|
|
4354
|
-
page.value =
|
|
4999
|
+
pageSize.value = clampListPageSize(size);
|
|
5000
|
+
page.value = LIST_FIRST_PAGE;
|
|
4355
5001
|
await load();
|
|
4356
5002
|
}
|
|
4357
5003
|
if (options.filter) {
|
|
4358
5004
|
(0, import_vue5.watch)(
|
|
4359
5005
|
options.filter,
|
|
4360
5006
|
() => {
|
|
4361
|
-
page.value =
|
|
5007
|
+
page.value = LIST_FIRST_PAGE;
|
|
4362
5008
|
void load();
|
|
4363
5009
|
},
|
|
4364
5010
|
{ deep: true }
|
|
@@ -4404,15 +5050,253 @@ function usePagination(rows, initialRowsPerPage = DEFAULT_ROWS_PER_PAGE) {
|
|
|
4404
5050
|
return { page, rowsPerPage, total, pagedRows };
|
|
4405
5051
|
}
|
|
4406
5052
|
|
|
4407
|
-
// src/vue/use-
|
|
5053
|
+
// src/vue/use-async-action.ts
|
|
4408
5054
|
var import_vue7 = require("vue");
|
|
5055
|
+
function report(announce) {
|
|
5056
|
+
try {
|
|
5057
|
+
announce();
|
|
5058
|
+
} catch (err) {
|
|
5059
|
+
queueMicrotask(() => {
|
|
5060
|
+
throw err;
|
|
5061
|
+
});
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
function useAsyncAction(fn, options = {}) {
|
|
5065
|
+
const pending = (0, import_vue7.ref)(false);
|
|
5066
|
+
const error = (0, import_vue7.ref)(null);
|
|
5067
|
+
const notify = options.notify ?? (0, import_vue7.inject)(SUPER_ADMIN_NOTIFY_KEY, void 0);
|
|
5068
|
+
const messages = useSaMessages("errors");
|
|
5069
|
+
const notifyOn = options.notifyOn ?? "error";
|
|
5070
|
+
let inFlight = 0;
|
|
5071
|
+
let generation = 0;
|
|
5072
|
+
async function run(...args) {
|
|
5073
|
+
const mine = ++generation;
|
|
5074
|
+
inFlight++;
|
|
5075
|
+
pending.value = true;
|
|
5076
|
+
error.value = null;
|
|
5077
|
+
try {
|
|
5078
|
+
const result = await fn(...args);
|
|
5079
|
+
await options.onSuccess?.(result);
|
|
5080
|
+
if (notifyOn === "both") {
|
|
5081
|
+
report(() => {
|
|
5082
|
+
const message = typeof options.successMessage === "function" ? options.successMessage() : options.successMessage;
|
|
5083
|
+
if (message) notify?.("positive", message);
|
|
5084
|
+
});
|
|
5085
|
+
}
|
|
5086
|
+
return { ok: true, value: result };
|
|
5087
|
+
} catch (err) {
|
|
5088
|
+
const adminError = toAdminError(err);
|
|
5089
|
+
if (mine === generation) error.value = adminError;
|
|
5090
|
+
if (notifyOn !== "none") {
|
|
5091
|
+
report(
|
|
5092
|
+
() => notify?.(
|
|
5093
|
+
"negative",
|
|
5094
|
+
options.errorMessage?.(adminError) ?? adminErrorMessage(adminError, messages.value)
|
|
5095
|
+
)
|
|
5096
|
+
);
|
|
5097
|
+
}
|
|
5098
|
+
return { ok: false, error: adminError };
|
|
5099
|
+
} finally {
|
|
5100
|
+
if (--inFlight === 0) pending.value = false;
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
return {
|
|
5104
|
+
run,
|
|
5105
|
+
pending,
|
|
5106
|
+
error,
|
|
5107
|
+
reset: () => {
|
|
5108
|
+
error.value = null;
|
|
5109
|
+
}
|
|
5110
|
+
};
|
|
5111
|
+
}
|
|
5112
|
+
|
|
5113
|
+
// src/vue/use-async-data.ts
|
|
5114
|
+
var import_vue8 = require("vue");
|
|
5115
|
+
function useAsyncData(fn, options) {
|
|
5116
|
+
const data = (0, import_vue8.ref)(options.initial);
|
|
5117
|
+
const pending = (0, import_vue8.ref)(false);
|
|
5118
|
+
const error = (0, import_vue8.ref)(null);
|
|
5119
|
+
let generation = 0;
|
|
5120
|
+
async function reload() {
|
|
5121
|
+
const mine = ++generation;
|
|
5122
|
+
pending.value = true;
|
|
5123
|
+
error.value = null;
|
|
5124
|
+
try {
|
|
5125
|
+
const result = await fn();
|
|
5126
|
+
if (mine !== generation) return;
|
|
5127
|
+
data.value = result;
|
|
5128
|
+
} catch (err) {
|
|
5129
|
+
if (mine !== generation) return;
|
|
5130
|
+
error.value = toAdminError(err);
|
|
5131
|
+
data.value = options.initial;
|
|
5132
|
+
} finally {
|
|
5133
|
+
if (mine === generation) pending.value = false;
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
5136
|
+
if (options.watch?.length) {
|
|
5137
|
+
(0, import_vue8.watch)(options.watch, () => void reload());
|
|
5138
|
+
}
|
|
5139
|
+
if (options.immediate !== false) {
|
|
5140
|
+
void Promise.resolve().then(() => reload());
|
|
5141
|
+
}
|
|
5142
|
+
return { data, pending, error, reload };
|
|
5143
|
+
}
|
|
5144
|
+
|
|
5145
|
+
// src/vue/resource-registry.ts
|
|
5146
|
+
var import_vue9 = require("vue");
|
|
5147
|
+
function boundWithOverride(def, http, readContext, override) {
|
|
5148
|
+
const context = override?.context ? () => ({ ...readContext(), ...override.context }) : readContext;
|
|
5149
|
+
const platform = bindResource(def, override?.http ?? http, context);
|
|
5150
|
+
if (!override?.ops) return platform;
|
|
5151
|
+
const result = { ...platform };
|
|
5152
|
+
for (const [name, wrap] of Object.entries(override.ops)) {
|
|
5153
|
+
if (typeof wrap !== "function") continue;
|
|
5154
|
+
if (!Object.hasOwn(platform, name)) {
|
|
5155
|
+
throw new Error(
|
|
5156
|
+
`createResourceRegistry: resource "${def.key}" has no operation "${name}" to override. It offers: ${Object.keys(platform).join(", ")}.`
|
|
5157
|
+
);
|
|
5158
|
+
}
|
|
5159
|
+
const next = platform[name];
|
|
5160
|
+
result[name] = (...args) => wrap(next, ...args);
|
|
5161
|
+
}
|
|
5162
|
+
return result;
|
|
5163
|
+
}
|
|
5164
|
+
function createResourceRegistry(options) {
|
|
5165
|
+
if (!options?.http) {
|
|
5166
|
+
throw new Error(
|
|
5167
|
+
"createResourceRegistry: `http` is required. Pass the HttpClient your app already uses \u2014 createAxiosHttpClient(instance) or createFetchHttpClient({ headers }) \u2014 so every platform request carries its auth."
|
|
5168
|
+
);
|
|
5169
|
+
}
|
|
5170
|
+
const readContext = typeof options.context === "function" ? options.context : () => options.context;
|
|
5171
|
+
const unknown = Object.keys(options.overrides ?? {}).filter(
|
|
5172
|
+
(key) => !Object.hasOwn(options.resources, key)
|
|
5173
|
+
);
|
|
5174
|
+
if (unknown.length > 0) {
|
|
5175
|
+
throw new Error(
|
|
5176
|
+
`createResourceRegistry: no resource named ${unknown.map((k) => `"${k}"`).join(", ")} to override. The registry holds: ${Object.keys(options.resources).join(", ")}.`
|
|
5177
|
+
);
|
|
5178
|
+
}
|
|
5179
|
+
const bound = /* @__PURE__ */ new Map();
|
|
5180
|
+
for (const [key, def] of Object.entries(options.resources)) {
|
|
5181
|
+
bound.set(key, boundWithOverride(def, options.http, readContext, options.overrides?.[key]));
|
|
5182
|
+
}
|
|
5183
|
+
return {
|
|
5184
|
+
get(key) {
|
|
5185
|
+
const ops = bound.get(key);
|
|
5186
|
+
if (!ops) {
|
|
5187
|
+
throw new Error(
|
|
5188
|
+
`useResource("${String(key)}"): no such resource. The registry offers: ${[...bound.keys()].join(", ")}.`
|
|
5189
|
+
);
|
|
5190
|
+
}
|
|
5191
|
+
return ops;
|
|
5192
|
+
},
|
|
5193
|
+
keys: () => [...bound.keys()]
|
|
5194
|
+
};
|
|
5195
|
+
}
|
|
5196
|
+
var SUPER_ADMIN_RESOURCES_KEY = /* @__PURE__ */ Symbol.for(
|
|
5197
|
+
"@saasicat/ui-vue/SUPER_ADMIN_RESOURCES"
|
|
5198
|
+
);
|
|
5199
|
+
function useResource(key) {
|
|
5200
|
+
const registry = (0, import_vue9.inject)(SUPER_ADMIN_RESOURCES_KEY, null);
|
|
5201
|
+
if (!registry) {
|
|
5202
|
+
throw new Error(
|
|
5203
|
+
`useResource("${key}"): no resource registry in scope. Either the component was not mounted inside createSuperAdminApp(), or the app did not pass an \`http\` client \u2014 the registry is not installed without one, because a bare fetch would send every request without your Authorization header and fail silently.`
|
|
5204
|
+
);
|
|
5205
|
+
}
|
|
5206
|
+
return registry.get(key);
|
|
5207
|
+
}
|
|
5208
|
+
|
|
5209
|
+
// src/vue/use-resource-list.ts
|
|
5210
|
+
var import_vue10 = require("vue");
|
|
5211
|
+
var DEFAULT_LIST_OP = "list";
|
|
5212
|
+
var EMPTY_PAGE = Object.freeze({
|
|
5213
|
+
items: Object.freeze([]),
|
|
5214
|
+
total: 0
|
|
5215
|
+
});
|
|
5216
|
+
function paginationCollision(key, op, claimed) {
|
|
5217
|
+
return new AdminError({
|
|
5218
|
+
message: `useResourceList("${key}", { op: "${op}" }): the filter carries ${claimed.join(" and ")}, which the list already sends. The filter is serialised after the pagination, so its value wins on the wire while page.value keeps the one goToPage() set \u2014 the list would report a page it is not showing. Drive the page with goToPage()/setPageSize() and leave those keys out of the filter.`
|
|
5219
|
+
});
|
|
5220
|
+
}
|
|
5221
|
+
function paginationKeysIn(filter) {
|
|
5222
|
+
return LIST_PAGINATION_PARAMS.filter((param) => isSentInQuery(filter[param]));
|
|
5223
|
+
}
|
|
5224
|
+
function readListOp(key, ops, name) {
|
|
5225
|
+
const op = Object.hasOwn(ops, name) ? ops[name] : void 0;
|
|
5226
|
+
if (typeof op !== "function") {
|
|
5227
|
+
throw new Error(
|
|
5228
|
+
`useResourceList("${key}", { op: "${name}" }): that resource has no such operation. It offers: ${Object.keys(ops).join(", ")}.`
|
|
5229
|
+
);
|
|
5230
|
+
}
|
|
5231
|
+
return op;
|
|
5232
|
+
}
|
|
5233
|
+
function useResourceList(key, opts) {
|
|
5234
|
+
const ops = useResource(key);
|
|
5235
|
+
const opName = opts?.op ?? DEFAULT_LIST_OP;
|
|
5236
|
+
const load = readListOp(key, ops, opName);
|
|
5237
|
+
if (opts?.filter) {
|
|
5238
|
+
const claimed = paginationKeysIn(opts.filter.value);
|
|
5239
|
+
if (claimed.length > 0) throw paginationCollision(key, opName, claimed);
|
|
5240
|
+
}
|
|
5241
|
+
const page = (0, import_vue10.ref)(LIST_FIRST_PAGE);
|
|
5242
|
+
const pageSize = (0, import_vue10.ref)(clampListPageSize(opts?.pageSize ?? LIST_PAGE_SIZE_DEFAULT));
|
|
5243
|
+
function query() {
|
|
5244
|
+
const filter = opts?.filter?.value;
|
|
5245
|
+
if (filter) {
|
|
5246
|
+
const claimed = paginationKeysIn(filter);
|
|
5247
|
+
if (claimed.length > 0) throw paginationCollision(key, opName, claimed);
|
|
5248
|
+
}
|
|
5249
|
+
return { page: page.value, pageSize: pageSize.value, filter };
|
|
5250
|
+
}
|
|
5251
|
+
const state = useAsyncData(() => load(query()), {
|
|
5252
|
+
initial: EMPTY_PAGE,
|
|
5253
|
+
immediate: opts?.immediate
|
|
5254
|
+
});
|
|
5255
|
+
(0, import_vue10.watch)(state.data, (answer) => {
|
|
5256
|
+
if (typeof answer.page === "number") page.value = answer.page;
|
|
5257
|
+
if (typeof answer.pageSize === "number") pageSize.value = answer.pageSize;
|
|
5258
|
+
});
|
|
5259
|
+
async function goToPage(next) {
|
|
5260
|
+
page.value = clampListPage(next);
|
|
5261
|
+
await state.reload();
|
|
5262
|
+
}
|
|
5263
|
+
async function setPageSize(size) {
|
|
5264
|
+
pageSize.value = clampListPageSize(size);
|
|
5265
|
+
page.value = LIST_FIRST_PAGE;
|
|
5266
|
+
await state.reload();
|
|
5267
|
+
}
|
|
5268
|
+
if (opts?.filter) {
|
|
5269
|
+
(0, import_vue10.watch)(
|
|
5270
|
+
opts.filter,
|
|
5271
|
+
() => {
|
|
5272
|
+
page.value = LIST_FIRST_PAGE;
|
|
5273
|
+
void state.reload();
|
|
5274
|
+
},
|
|
5275
|
+
{ deep: true }
|
|
5276
|
+
);
|
|
5277
|
+
}
|
|
5278
|
+
return {
|
|
5279
|
+
items: (0, import_vue10.computed)(() => state.data.value.items),
|
|
5280
|
+
total: (0, import_vue10.computed)(() => state.data.value.total ?? state.data.value.items.length),
|
|
5281
|
+
page,
|
|
5282
|
+
pageSize,
|
|
5283
|
+
pending: state.pending,
|
|
5284
|
+
error: state.error,
|
|
5285
|
+
reload: state.reload,
|
|
5286
|
+
goToPage,
|
|
5287
|
+
setPageSize
|
|
5288
|
+
};
|
|
5289
|
+
}
|
|
5290
|
+
|
|
5291
|
+
// src/vue/use-tenants.ts
|
|
5292
|
+
var import_vue11 = require("vue");
|
|
4409
5293
|
function useTenants(options) {
|
|
4410
5294
|
if (!options?.endpoint) {
|
|
4411
5295
|
throw new Error(
|
|
4412
5296
|
'useTenants: `endpoint` is required (e.g. "/api/v1/admin/tenants" or "/api/admin/tenants"). The platform has no default because apps use different globalPrefix conventions.'
|
|
4413
5297
|
);
|
|
4414
5298
|
}
|
|
4415
|
-
const filter = options.filter ?? (0,
|
|
5299
|
+
const filter = options.filter ?? (0, import_vue11.ref)({});
|
|
4416
5300
|
const list = useApiList({
|
|
4417
5301
|
endpoint: options.endpoint,
|
|
4418
5302
|
filter,
|
|
@@ -4424,14 +5308,14 @@ function useTenants(options) {
|
|
|
4424
5308
|
}
|
|
4425
5309
|
|
|
4426
5310
|
// src/vue/use-audit-entries.ts
|
|
4427
|
-
var
|
|
5311
|
+
var import_vue12 = require("vue");
|
|
4428
5312
|
function useAuditEntries(options) {
|
|
4429
5313
|
if (!options?.endpoint) {
|
|
4430
5314
|
throw new Error(
|
|
4431
5315
|
'useAuditEntries: `endpoint` is required (e.g. "/api/admin/audit" or "/api/v1/admin/audit"). The platform has no default because apps use different globalPrefix conventions.'
|
|
4432
5316
|
);
|
|
4433
5317
|
}
|
|
4434
|
-
const filter = options.filter ?? (0,
|
|
5318
|
+
const filter = options.filter ?? (0, import_vue12.ref)({});
|
|
4435
5319
|
const list = useApiList({
|
|
4436
5320
|
endpoint: options.endpoint,
|
|
4437
5321
|
filter,
|
|
@@ -4443,7 +5327,7 @@ function useAuditEntries(options) {
|
|
|
4443
5327
|
}
|
|
4444
5328
|
|
|
4445
5329
|
// src/vue/use-entitlement.ts
|
|
4446
|
-
var
|
|
5330
|
+
var import_vue13 = require("vue");
|
|
4447
5331
|
function useEntitlement(options) {
|
|
4448
5332
|
if (!options?.endpoint) {
|
|
4449
5333
|
throw new Error(
|
|
@@ -4452,9 +5336,9 @@ function useEntitlement(options) {
|
|
|
4452
5336
|
}
|
|
4453
5337
|
const endpoint = options.endpoint;
|
|
4454
5338
|
const http = options.http ?? defaultHttpClient();
|
|
4455
|
-
const entitlement = (0,
|
|
4456
|
-
const loading = (0,
|
|
4457
|
-
const error = (0,
|
|
5339
|
+
const entitlement = (0, import_vue13.ref)(null);
|
|
5340
|
+
const loading = (0, import_vue13.ref)(false);
|
|
5341
|
+
const error = (0, import_vue13.ref)(null);
|
|
4458
5342
|
async function load() {
|
|
4459
5343
|
loading.value = true;
|
|
4460
5344
|
error.value = null;
|
|
@@ -4484,15 +5368,15 @@ function useEntitlement(options) {
|
|
|
4484
5368
|
}
|
|
4485
5369
|
|
|
4486
5370
|
// src/vue/use-tenant-manifest.ts
|
|
4487
|
-
var
|
|
5371
|
+
var import_vue14 = require("vue");
|
|
4488
5372
|
function useTenantManifest(options) {
|
|
4489
5373
|
if (!options?.endpoint) {
|
|
4490
5374
|
throw new Error('useTenantManifest: `endpoint` is required (e.g. "/api/tenant/manifest").');
|
|
4491
5375
|
}
|
|
4492
5376
|
const http = options.http ?? defaultHttpClient();
|
|
4493
|
-
const manifest = (0,
|
|
4494
|
-
const loading = (0,
|
|
4495
|
-
const error = (0,
|
|
5377
|
+
const manifest = (0, import_vue14.ref)(null);
|
|
5378
|
+
const loading = (0, import_vue14.ref)(false);
|
|
5379
|
+
const error = (0, import_vue14.ref)(null);
|
|
4496
5380
|
async function load() {
|
|
4497
5381
|
loading.value = true;
|
|
4498
5382
|
error.value = null;
|
|
@@ -4526,7 +5410,7 @@ function useTenantManifest(options) {
|
|
|
4526
5410
|
}
|
|
4527
5411
|
|
|
4528
5412
|
// src/vue/entitlement-provider.ts
|
|
4529
|
-
var
|
|
5413
|
+
var import_vue15 = require("vue");
|
|
4530
5414
|
var ENTITLEMENT_INJECTION_KEY = /* @__PURE__ */ Symbol.for(
|
|
4531
5415
|
"@saasicat/ui-vue/ENTITLEMENT"
|
|
4532
5416
|
);
|
|
@@ -4534,7 +5418,7 @@ function provideEntitlement(app, entitlement) {
|
|
|
4534
5418
|
app.provide(ENTITLEMENT_INJECTION_KEY, entitlement);
|
|
4535
5419
|
}
|
|
4536
5420
|
function useInjectedEntitlement() {
|
|
4537
|
-
return (0,
|
|
5421
|
+
return (0, import_vue15.inject)(ENTITLEMENT_INJECTION_KEY, null);
|
|
4538
5422
|
}
|
|
4539
5423
|
|
|
4540
5424
|
// src/vue/feature-router-guard.ts
|
|
@@ -4563,15 +5447,15 @@ function buildFeatureRouterGuard(options) {
|
|
|
4563
5447
|
}
|
|
4564
5448
|
|
|
4565
5449
|
// src/vue/use-tenant-billing-catalog.ts
|
|
4566
|
-
var
|
|
5450
|
+
var import_vue16 = require("vue");
|
|
4567
5451
|
function useTenantBillingCatalog(options = {}) {
|
|
4568
5452
|
const apiPrefix = trimTrailingSlashes(options.apiPrefix ?? "/billing");
|
|
4569
5453
|
const http = options.http ?? defaultHttpClient();
|
|
4570
|
-
const plans = (0,
|
|
4571
|
-
const featureRegistry = (0,
|
|
4572
|
-
const bundles = (0,
|
|
4573
|
-
const loading = (0,
|
|
4574
|
-
const error = (0,
|
|
5454
|
+
const plans = (0, import_vue16.ref)(null);
|
|
5455
|
+
const featureRegistry = (0, import_vue16.ref)(null);
|
|
5456
|
+
const bundles = (0, import_vue16.ref)(null);
|
|
5457
|
+
const loading = (0, import_vue16.ref)(false);
|
|
5458
|
+
const error = (0, import_vue16.ref)(null);
|
|
4575
5459
|
async function fetchJson(path) {
|
|
4576
5460
|
const res = await http(`${apiPrefix}${path}`, { method: "GET" });
|
|
4577
5461
|
if (res.status !== 200) {
|
|
@@ -4621,14 +5505,14 @@ function useTenantBillingCatalog(options = {}) {
|
|
|
4621
5505
|
}
|
|
4622
5506
|
|
|
4623
5507
|
// src/vue/use-tenant-billing.ts
|
|
4624
|
-
var
|
|
5508
|
+
var import_vue17 = require("vue");
|
|
4625
5509
|
function useTenantBilling(options = {}) {
|
|
4626
5510
|
const apiPrefix = trimTrailingSlashes(options.apiPrefix ?? "/billing");
|
|
4627
5511
|
const http = options.http ?? defaultHttpClient();
|
|
4628
|
-
const usage = (0,
|
|
4629
|
-
const loading = (0,
|
|
4630
|
-
const error = (0,
|
|
4631
|
-
const subscriptionBundles = (0,
|
|
5512
|
+
const usage = (0, import_vue17.ref)(null);
|
|
5513
|
+
const loading = (0, import_vue17.ref)(false);
|
|
5514
|
+
const error = (0, import_vue17.ref)(null);
|
|
5515
|
+
const subscriptionBundles = (0, import_vue17.ref)([]);
|
|
4632
5516
|
function authHeaders() {
|
|
4633
5517
|
const headers = {};
|
|
4634
5518
|
const token = options.getAuthToken?.();
|
|
@@ -4762,7 +5646,7 @@ function useTenantBilling(options = {}) {
|
|
|
4762
5646
|
}
|
|
4763
5647
|
|
|
4764
5648
|
// src/vue/use-subscription-draft.ts
|
|
4765
|
-
var
|
|
5649
|
+
var import_vue18 = require("vue");
|
|
4766
5650
|
var import_types13 = require("@saasicat/types");
|
|
4767
5651
|
var DEFAULT_YEARLY_FACTOR = 10;
|
|
4768
5652
|
function unwrap(source) {
|
|
@@ -4779,27 +5663,27 @@ function priceForCycle(monthlyNet, yearlyNet, cycle, yearlyFactor) {
|
|
|
4779
5663
|
}
|
|
4780
5664
|
function useSubscriptionDraft(options) {
|
|
4781
5665
|
const yearlyFactor = options.yearlyFactor ?? DEFAULT_YEARLY_FACTOR;
|
|
4782
|
-
const plansRef = (0,
|
|
4783
|
-
const subscriptionBundlesRef = (0,
|
|
5666
|
+
const plansRef = (0, import_vue18.computed)(() => unwrap(options.plans) ?? []);
|
|
5667
|
+
const subscriptionBundlesRef = (0, import_vue18.computed)(
|
|
4784
5668
|
() => unwrap(options.subscriptionBundles ?? null) ?? []
|
|
4785
5669
|
);
|
|
4786
|
-
const plan = (0,
|
|
4787
|
-
const cycle = (0,
|
|
4788
|
-
const selectedBundleVersionIds = (0,
|
|
5670
|
+
const plan = (0, import_vue18.ref)(options.initialPlan ?? null);
|
|
5671
|
+
const cycle = (0, import_vue18.ref)(options.initialCycle ?? "MONTHLY");
|
|
5672
|
+
const selectedBundleVersionIds = (0, import_vue18.ref)(
|
|
4789
5673
|
new Set(options.initialBundleVersionIds ?? [])
|
|
4790
5674
|
);
|
|
4791
|
-
const promoCode = (0,
|
|
4792
|
-
const promoState = (0,
|
|
4793
|
-
const selectedPlan = (0,
|
|
5675
|
+
const promoCode = (0, import_vue18.ref)("");
|
|
5676
|
+
const promoState = (0, import_vue18.ref)({ status: "idle", preview: null, message: "" });
|
|
5677
|
+
const selectedPlan = (0, import_vue18.computed)(() => {
|
|
4794
5678
|
if (!plan.value) return null;
|
|
4795
5679
|
return plansRef.value.find((p) => p.id === plan.value) ?? null;
|
|
4796
5680
|
});
|
|
4797
|
-
const selectedBundles = (0,
|
|
5681
|
+
const selectedBundles = (0, import_vue18.computed)(
|
|
4798
5682
|
() => subscriptionBundlesRef.value.filter(
|
|
4799
5683
|
(b) => selectedBundleVersionIds.value.has(b.bundleVersionId)
|
|
4800
5684
|
)
|
|
4801
5685
|
);
|
|
4802
|
-
const chargeableBundles = (0,
|
|
5686
|
+
const chargeableBundles = (0, import_vue18.computed)(() => {
|
|
4803
5687
|
const planFeatures = selectedPlan.value?.features ?? [];
|
|
4804
5688
|
const selected = selectedBundles.value;
|
|
4805
5689
|
const keptIds = new Set(
|
|
@@ -4807,7 +5691,7 @@ function useSubscriptionDraft(options) {
|
|
|
4807
5691
|
);
|
|
4808
5692
|
return selected.filter((b) => keptIds.has(b.bundleVersionId));
|
|
4809
5693
|
});
|
|
4810
|
-
const activeFeatures = (0,
|
|
5694
|
+
const activeFeatures = (0, import_vue18.computed)(() => {
|
|
4811
5695
|
const result = /* @__PURE__ */ new Set();
|
|
4812
5696
|
const planObj = selectedPlan.value;
|
|
4813
5697
|
if (planObj) {
|
|
@@ -4818,7 +5702,7 @@ function useSubscriptionDraft(options) {
|
|
|
4818
5702
|
}
|
|
4819
5703
|
return result;
|
|
4820
5704
|
});
|
|
4821
|
-
const pricing = (0,
|
|
5705
|
+
const pricing = (0, import_vue18.computed)(() => {
|
|
4822
5706
|
const cyc = cycle.value;
|
|
4823
5707
|
const planObj = selectedPlan.value;
|
|
4824
5708
|
const planNet = planObj ? priceForCycle(planObj.monthlyNet ?? 0, planObj.yearlyNet, cyc, yearlyFactor) : 0;
|
|
@@ -4878,7 +5762,7 @@ function useSubscriptionDraft(options) {
|
|
|
4878
5762
|
}
|
|
4879
5763
|
};
|
|
4880
5764
|
});
|
|
4881
|
-
const isDirty = (0,
|
|
5765
|
+
const isDirty = (0, import_vue18.computed)(() => {
|
|
4882
5766
|
return selectedBundleVersionIds.value.size > 0 || promoState.value.status === "valid";
|
|
4883
5767
|
});
|
|
4884
5768
|
function setPlan(planId) {
|
|
@@ -4954,7 +5838,7 @@ function useSubscriptionDraft(options) {
|
|
|
4954
5838
|
}
|
|
4955
5839
|
|
|
4956
5840
|
// src/vue/use-bulk-publish.ts
|
|
4957
|
-
var
|
|
5841
|
+
var import_vue19 = require("vue");
|
|
4958
5842
|
function useBulkPublish(options) {
|
|
4959
5843
|
const msg = useSaMessages("planVersions");
|
|
4960
5844
|
if (!options?.endpoints) {
|
|
@@ -4964,15 +5848,15 @@ function useBulkPublish(options) {
|
|
|
4964
5848
|
}
|
|
4965
5849
|
const http = options.http ?? defaultHttpClient();
|
|
4966
5850
|
const endpoints = options.endpoints;
|
|
4967
|
-
const items = (0,
|
|
4968
|
-
const successCount = (0,
|
|
4969
|
-
const failureCount = (0,
|
|
4970
|
-
const progress = (0,
|
|
5851
|
+
const items = (0, import_vue19.ref)([]);
|
|
5852
|
+
const successCount = (0, import_vue19.computed)(() => items.value.filter((i) => i.status === "published").length);
|
|
5853
|
+
const failureCount = (0, import_vue19.computed)(() => items.value.filter((i) => i.status === "failed").length);
|
|
5854
|
+
const progress = (0, import_vue19.computed)(() => {
|
|
4971
5855
|
if (items.value.length === 0) return 0;
|
|
4972
5856
|
const done2 = successCount.value + failureCount.value;
|
|
4973
5857
|
return done2 / items.value.length;
|
|
4974
5858
|
});
|
|
4975
|
-
const done = (0,
|
|
5859
|
+
const done = (0, import_vue19.computed)(
|
|
4976
5860
|
() => items.value.length > 0 && items.value.every((i) => i.status === "published" || i.status === "failed")
|
|
4977
5861
|
);
|
|
4978
5862
|
function setItems(next) {
|
|
@@ -5030,7 +5914,7 @@ function useBulkPublish(options) {
|
|
|
5030
5914
|
}
|
|
5031
5915
|
|
|
5032
5916
|
// src/vue/use-plan-editor.ts
|
|
5033
|
-
var
|
|
5917
|
+
var import_vue20 = require("vue");
|
|
5034
5918
|
var PlannedOnlyFeatureError = class extends Error {
|
|
5035
5919
|
constructor(violations) {
|
|
5036
5920
|
super(
|
|
@@ -5050,10 +5934,10 @@ function tierWeight(tier) {
|
|
|
5050
5934
|
function usePlanEditor(manifest, options = {}) {
|
|
5051
5935
|
const baseSet = new Set(options.baseFeatures ?? []);
|
|
5052
5936
|
const nonRegressive = options.nonRegressive ?? true;
|
|
5053
|
-
const selectedFeatures = (0,
|
|
5937
|
+
const selectedFeatures = (0, import_vue20.ref)(new Set(options.initialFeatures ?? []));
|
|
5054
5938
|
const catalogFeatures = manifest.planCatalogSnapshot.features ?? [];
|
|
5055
5939
|
const plannedOnlySet = new Set(catalogFeatures.filter((f) => f.plannedOnly).map((f) => f.key));
|
|
5056
|
-
const availableFeatures = (0,
|
|
5940
|
+
const availableFeatures = (0, import_vue20.computed)(() => {
|
|
5057
5941
|
return catalogFeatures.map((def) => {
|
|
5058
5942
|
const isSelected = selectedFeatures.value.has(def.key);
|
|
5059
5943
|
const isPlannedOnly = !!def.plannedOnly;
|
|
@@ -5062,7 +5946,7 @@ function usePlanEditor(manifest, options = {}) {
|
|
|
5062
5946
|
return { def, isSelected, isPlannedOnly, isInherited, canToggle };
|
|
5063
5947
|
});
|
|
5064
5948
|
});
|
|
5065
|
-
const featuresByTier = (0,
|
|
5949
|
+
const featuresByTier = (0, import_vue20.computed)(() => {
|
|
5066
5950
|
const groups = /* @__PURE__ */ new Map();
|
|
5067
5951
|
for (const row of availableFeatures.value) {
|
|
5068
5952
|
const tier = row.def.tier ?? "OTHER";
|
|
@@ -5107,12 +5991,12 @@ function usePlanEditor(manifest, options = {}) {
|
|
|
5107
5991
|
}
|
|
5108
5992
|
|
|
5109
5993
|
// src/vue/use-public-boot.ts
|
|
5110
|
-
var
|
|
5994
|
+
var import_vue21 = require("vue");
|
|
5111
5995
|
function usePublicBoot(options) {
|
|
5112
5996
|
const loader = new BootLoader(options);
|
|
5113
|
-
const boot = (0,
|
|
5114
|
-
const loading = (0,
|
|
5115
|
-
const error = (0,
|
|
5997
|
+
const boot = (0, import_vue21.ref)(null);
|
|
5998
|
+
const loading = (0, import_vue21.ref)(false);
|
|
5999
|
+
const error = (0, import_vue21.ref)(null);
|
|
5116
6000
|
async function load() {
|
|
5117
6001
|
loading.value = true;
|
|
5118
6002
|
error.value = null;
|
|
@@ -5129,27 +6013,27 @@ function usePublicBoot(options) {
|
|
|
5129
6013
|
}
|
|
5130
6014
|
|
|
5131
6015
|
// src/vue/use-discovery.ts
|
|
5132
|
-
var
|
|
6016
|
+
var import_vue22 = require("vue");
|
|
5133
6017
|
var DiscoveryLoadError = class extends Error {
|
|
5134
6018
|
constructor(status, message) {
|
|
5135
6019
|
super(message);
|
|
5136
6020
|
this.status = status;
|
|
5137
6021
|
this.name = "DiscoveryLoadError";
|
|
6022
|
+
markPlatformError(this);
|
|
5138
6023
|
}
|
|
5139
6024
|
status;
|
|
5140
6025
|
};
|
|
5141
6026
|
function useDiscovery(options) {
|
|
5142
|
-
const msg = useSaMessages("discovery");
|
|
5143
6027
|
if (!options?.endpoint) {
|
|
5144
6028
|
throw new Error(
|
|
5145
6029
|
'useDiscovery: `endpoint` is required (e.g. "/api/admin/discovery" or "/api/v1/admin/discovery"). The platform has no default because apps use different globalPrefix conventions.'
|
|
5146
6030
|
);
|
|
5147
6031
|
}
|
|
5148
6032
|
const http = options.http ?? defaultHttpClient();
|
|
5149
|
-
const snapshot = (0,
|
|
5150
|
-
const etag = (0,
|
|
5151
|
-
const loading = (0,
|
|
5152
|
-
const error = (0,
|
|
6033
|
+
const snapshot = (0, import_vue22.ref)(null);
|
|
6034
|
+
const etag = (0, import_vue22.ref)(null);
|
|
6035
|
+
const loading = (0, import_vue22.ref)(false);
|
|
6036
|
+
const error = (0, import_vue22.ref)(null);
|
|
5153
6037
|
async function fetchSnapshot(includeEtag) {
|
|
5154
6038
|
loading.value = true;
|
|
5155
6039
|
error.value = null;
|
|
@@ -5161,13 +6045,19 @@ function useDiscovery(options) {
|
|
|
5161
6045
|
headers["If-None-Match"] = etag.value;
|
|
5162
6046
|
}
|
|
5163
6047
|
const res = await http(options.endpoint, { method: "GET", headers });
|
|
6048
|
+
requireServerAnswer(
|
|
6049
|
+
res.status,
|
|
6050
|
+
"GET",
|
|
6051
|
+
options.endpoint,
|
|
6052
|
+
(diagnostic) => new DiscoveryLoadError(res.status, diagnostic)
|
|
6053
|
+
);
|
|
5164
6054
|
if (res.status === 304) {
|
|
5165
6055
|
return;
|
|
5166
6056
|
}
|
|
5167
6057
|
if (res.status !== 200) {
|
|
5168
6058
|
throw new DiscoveryLoadError(
|
|
5169
6059
|
res.status,
|
|
5170
|
-
|
|
6060
|
+
`Discovery endpoint responded with HTTP ${res.status}`
|
|
5171
6061
|
);
|
|
5172
6062
|
}
|
|
5173
6063
|
const body = await res.json();
|
|
@@ -5193,11 +6083,18 @@ function useDiscovery(options) {
|
|
|
5193
6083
|
const headers = { "content-type": "application/json" };
|
|
5194
6084
|
const token = options.getAuthToken?.();
|
|
5195
6085
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
5196
|
-
const
|
|
6086
|
+
const rescanUrl = `${options.endpoint}/rescan`;
|
|
6087
|
+
const res = await http(rescanUrl, { method: "POST", headers });
|
|
6088
|
+
requireServerAnswer(
|
|
6089
|
+
res.status,
|
|
6090
|
+
"POST",
|
|
6091
|
+
rescanUrl,
|
|
6092
|
+
(diagnostic) => new DiscoveryLoadError(res.status, diagnostic)
|
|
6093
|
+
);
|
|
5197
6094
|
if (res.status !== 200 && res.status !== 201) {
|
|
5198
6095
|
throw new DiscoveryLoadError(
|
|
5199
6096
|
res.status,
|
|
5200
|
-
|
|
6097
|
+
`Discovery rescan responded with HTTP ${res.status}`
|
|
5201
6098
|
);
|
|
5202
6099
|
}
|
|
5203
6100
|
const body = await res.json();
|
|
@@ -5216,13 +6113,14 @@ function useDiscovery(options) {
|
|
|
5216
6113
|
}
|
|
5217
6114
|
|
|
5218
6115
|
// src/vue/use-catalog-entries.ts
|
|
5219
|
-
var
|
|
6116
|
+
var import_vue23 = require("vue");
|
|
5220
6117
|
var CatalogEntriesApiError = class extends Error {
|
|
5221
6118
|
constructor(status, body, message) {
|
|
5222
6119
|
super(message);
|
|
5223
6120
|
this.status = status;
|
|
5224
6121
|
this.body = body;
|
|
5225
6122
|
this.name = "CatalogEntriesApiError";
|
|
6123
|
+
markPlatformError(this);
|
|
5226
6124
|
}
|
|
5227
6125
|
status;
|
|
5228
6126
|
body;
|
|
@@ -5234,13 +6132,12 @@ function useCatalogEntries(options) {
|
|
|
5234
6132
|
if (!options?.projectKey) {
|
|
5235
6133
|
throw new Error("useCatalogEntries: `projectKey` is required.");
|
|
5236
6134
|
}
|
|
5237
|
-
const msg = useSaMessages("discovery");
|
|
5238
6135
|
const http = options.http ?? defaultHttpClient();
|
|
5239
|
-
const capabilities = (0,
|
|
5240
|
-
const features = (0,
|
|
5241
|
-
const quotas = (0,
|
|
5242
|
-
const loading = (0,
|
|
5243
|
-
const error = (0,
|
|
6136
|
+
const capabilities = (0, import_vue23.ref)([]);
|
|
6137
|
+
const features = (0, import_vue23.ref)([]);
|
|
6138
|
+
const quotas = (0, import_vue23.ref)([]);
|
|
6139
|
+
const loading = (0, import_vue23.ref)(false);
|
|
6140
|
+
const error = (0, import_vue23.ref)(null);
|
|
5244
6141
|
const base = `${options.adminEndpoint}/catalog`;
|
|
5245
6142
|
const pk = encodeURIComponent(options.projectKey);
|
|
5246
6143
|
function authHeaders() {
|
|
@@ -5248,18 +6145,25 @@ function useCatalogEntries(options) {
|
|
|
5248
6145
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
5249
6146
|
}
|
|
5250
6147
|
async function fetchJson(url, init) {
|
|
6148
|
+
const method = init?.method ?? "GET";
|
|
5251
6149
|
const res = await http(url, {
|
|
5252
|
-
method
|
|
6150
|
+
method,
|
|
5253
6151
|
headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
|
|
5254
6152
|
body: init?.body
|
|
5255
6153
|
});
|
|
6154
|
+
requireServerAnswer(
|
|
6155
|
+
res.status,
|
|
6156
|
+
method,
|
|
6157
|
+
url,
|
|
6158
|
+
(diagnostic) => new CatalogEntriesApiError(res.status, null, diagnostic)
|
|
6159
|
+
);
|
|
5256
6160
|
if (res.status === 204) return null;
|
|
5257
6161
|
const body = await res.json().catch(() => null);
|
|
5258
6162
|
if (res.status >= 400) {
|
|
5259
6163
|
throw new CatalogEntriesApiError(
|
|
5260
6164
|
res.status,
|
|
5261
6165
|
body,
|
|
5262
|
-
|
|
6166
|
+
`Catalog entries API responded with HTTP ${res.status}`
|
|
5263
6167
|
);
|
|
5264
6168
|
}
|
|
5265
6169
|
return body;
|
|
@@ -5287,7 +6191,8 @@ function useCatalogEntries(options) {
|
|
|
5287
6191
|
`${base}/features/${encodeURIComponent(featureKey)}/review?projectKey=${pk}`,
|
|
5288
6192
|
{ method: "PATCH", body: JSON.stringify(data) }
|
|
5289
6193
|
);
|
|
5290
|
-
if (!updated)
|
|
6194
|
+
if (!updated)
|
|
6195
|
+
throw markEmptyResponse(new CatalogEntriesApiError(0, null, "Review returned no body"));
|
|
5291
6196
|
features.value = features.value.map((f) => f.featureKey === featureKey ? updated : f);
|
|
5292
6197
|
return updated;
|
|
5293
6198
|
}
|
|
@@ -5296,7 +6201,8 @@ function useCatalogEntries(options) {
|
|
|
5296
6201
|
`${base}/quotas/${encodeURIComponent(quotaKey)}/review?projectKey=${pk}`,
|
|
5297
6202
|
{ method: "PATCH", body: JSON.stringify(data) }
|
|
5298
6203
|
);
|
|
5299
|
-
if (!updated)
|
|
6204
|
+
if (!updated)
|
|
6205
|
+
throw markEmptyResponse(new CatalogEntriesApiError(0, null, "Review returned no body"));
|
|
5300
6206
|
quotas.value = quotas.value.map((q) => q.quotaKey === quotaKey ? updated : q);
|
|
5301
6207
|
return updated;
|
|
5302
6208
|
}
|
|
@@ -5305,7 +6211,8 @@ function useCatalogEntries(options) {
|
|
|
5305
6211
|
`${base}/features/${encodeURIComponent(featureKey)}/i18n?projectKey=${pk}`,
|
|
5306
6212
|
{ method: "PATCH", body: JSON.stringify({ i18n }) }
|
|
5307
6213
|
);
|
|
5308
|
-
if (!updated)
|
|
6214
|
+
if (!updated)
|
|
6215
|
+
throw markEmptyResponse(new CatalogEntriesApiError(0, null, "i18n returned no body"));
|
|
5309
6216
|
features.value = features.value.map((f) => f.featureKey === featureKey ? updated : f);
|
|
5310
6217
|
return updated;
|
|
5311
6218
|
}
|
|
@@ -5314,7 +6221,8 @@ function useCatalogEntries(options) {
|
|
|
5314
6221
|
`${base}/quotas/${encodeURIComponent(quotaKey)}/i18n?projectKey=${pk}`,
|
|
5315
6222
|
{ method: "PATCH", body: JSON.stringify({ i18n }) }
|
|
5316
6223
|
);
|
|
5317
|
-
if (!updated)
|
|
6224
|
+
if (!updated)
|
|
6225
|
+
throw markEmptyResponse(new CatalogEntriesApiError(0, null, "i18n returned no body"));
|
|
5318
6226
|
quotas.value = quotas.value.map((q) => q.quotaKey === quotaKey ? updated : q);
|
|
5319
6227
|
return updated;
|
|
5320
6228
|
}
|
|
@@ -5323,7 +6231,8 @@ function useCatalogEntries(options) {
|
|
|
5323
6231
|
`${base}/features/${encodeURIComponent(featureKey)}?projectKey=${pk}`,
|
|
5324
6232
|
{ method: "PATCH", body: JSON.stringify(data) }
|
|
5325
6233
|
);
|
|
5326
|
-
if (!updated)
|
|
6234
|
+
if (!updated)
|
|
6235
|
+
throw markEmptyResponse(new CatalogEntriesApiError(0, null, "Base returned no body"));
|
|
5327
6236
|
features.value = features.value.map((f) => f.featureKey === featureKey ? updated : f);
|
|
5328
6237
|
return updated;
|
|
5329
6238
|
}
|
|
@@ -5332,7 +6241,8 @@ function useCatalogEntries(options) {
|
|
|
5332
6241
|
`${base}/quotas/${encodeURIComponent(quotaKey)}?projectKey=${pk}`,
|
|
5333
6242
|
{ method: "PATCH", body: JSON.stringify(data) }
|
|
5334
6243
|
);
|
|
5335
|
-
if (!updated)
|
|
6244
|
+
if (!updated)
|
|
6245
|
+
throw markEmptyResponse(new CatalogEntriesApiError(0, null, "Base returned no body"));
|
|
5336
6246
|
quotas.value = quotas.value.map((q) => q.quotaKey === quotaKey ? updated : q);
|
|
5337
6247
|
return updated;
|
|
5338
6248
|
}
|
|
@@ -5341,7 +6251,8 @@ function useCatalogEntries(options) {
|
|
|
5341
6251
|
method: "POST",
|
|
5342
6252
|
body: JSON.stringify({ snapshot })
|
|
5343
6253
|
});
|
|
5344
|
-
if (!result)
|
|
6254
|
+
if (!result)
|
|
6255
|
+
throw markEmptyResponse(new CatalogEntriesApiError(0, null, "Sync returned no body"));
|
|
5345
6256
|
await load();
|
|
5346
6257
|
return result;
|
|
5347
6258
|
}
|
|
@@ -5366,13 +6277,14 @@ function useCatalogEntries(options) {
|
|
|
5366
6277
|
}
|
|
5367
6278
|
|
|
5368
6279
|
// src/vue/use-bundles.ts
|
|
5369
|
-
var
|
|
6280
|
+
var import_vue24 = require("vue");
|
|
5370
6281
|
var BundlesApiError = class extends Error {
|
|
5371
6282
|
constructor(status, body, message) {
|
|
5372
6283
|
super(message);
|
|
5373
6284
|
this.status = status;
|
|
5374
6285
|
this.body = body;
|
|
5375
6286
|
this.name = "BundlesApiError";
|
|
6287
|
+
markPlatformError(this);
|
|
5376
6288
|
}
|
|
5377
6289
|
status;
|
|
5378
6290
|
body;
|
|
@@ -5387,20 +6299,27 @@ function useBundles(options) {
|
|
|
5387
6299
|
throw new Error("useBundles: `projectKey` is required.");
|
|
5388
6300
|
}
|
|
5389
6301
|
const http = options.http ?? defaultHttpClient();
|
|
5390
|
-
const bundles = (0,
|
|
5391
|
-
const loading = (0,
|
|
5392
|
-
const error = (0,
|
|
6302
|
+
const bundles = (0, import_vue24.ref)([]);
|
|
6303
|
+
const loading = (0, import_vue24.ref)(false);
|
|
6304
|
+
const error = (0, import_vue24.ref)(null);
|
|
5393
6305
|
const baseUrl = `${options.adminEndpoint}/catalog/bundles`;
|
|
5394
6306
|
function authHeaders() {
|
|
5395
6307
|
const token = options.getAuthToken?.();
|
|
5396
6308
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
5397
6309
|
}
|
|
5398
6310
|
async function fetchJson(url, init) {
|
|
6311
|
+
const method = init?.method ?? "GET";
|
|
5399
6312
|
const res = await http(url, {
|
|
5400
|
-
method
|
|
6313
|
+
method,
|
|
5401
6314
|
headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
|
|
5402
6315
|
body: init?.body
|
|
5403
6316
|
});
|
|
6317
|
+
requireServerAnswer(
|
|
6318
|
+
res.status,
|
|
6319
|
+
method,
|
|
6320
|
+
url,
|
|
6321
|
+
(diagnostic) => new BundlesApiError(res.status, null, diagnostic)
|
|
6322
|
+
);
|
|
5404
6323
|
if (res.status === 204) return null;
|
|
5405
6324
|
const body = await res.json().catch(() => null);
|
|
5406
6325
|
if (res.status >= 400) {
|
|
@@ -5431,7 +6350,8 @@ function useBundles(options) {
|
|
|
5431
6350
|
method: "POST",
|
|
5432
6351
|
body: JSON.stringify(data)
|
|
5433
6352
|
});
|
|
5434
|
-
if (!created)
|
|
6353
|
+
if (!created)
|
|
6354
|
+
throw markEmptyResponse(new BundlesApiError(0, null, "Create returned no body"));
|
|
5435
6355
|
bundles.value = [...bundles.value, created];
|
|
5436
6356
|
return created;
|
|
5437
6357
|
}
|
|
@@ -5440,7 +6360,8 @@ function useBundles(options) {
|
|
|
5440
6360
|
method: "PATCH",
|
|
5441
6361
|
body: JSON.stringify(data)
|
|
5442
6362
|
});
|
|
5443
|
-
if (!updated)
|
|
6363
|
+
if (!updated)
|
|
6364
|
+
throw markEmptyResponse(new BundlesApiError(0, null, "Update returned no body"));
|
|
5444
6365
|
bundles.value = bundles.value.map((b) => b.id === bundleId ? updated : b);
|
|
5445
6366
|
return updated;
|
|
5446
6367
|
}
|
|
@@ -5461,9 +6382,9 @@ function useBundleVersions(options) {
|
|
|
5461
6382
|
throw new Error("useBundleVersions: `bundleId` is required.");
|
|
5462
6383
|
}
|
|
5463
6384
|
const http = options.http ?? defaultHttpClient();
|
|
5464
|
-
const versions = (0,
|
|
5465
|
-
const loading = (0,
|
|
5466
|
-
const error = (0,
|
|
6385
|
+
const versions = (0, import_vue24.ref)([]);
|
|
6386
|
+
const loading = (0, import_vue24.ref)(false);
|
|
6387
|
+
const error = (0, import_vue24.ref)(null);
|
|
5467
6388
|
const bundleVersionsUrl = `${options.adminEndpoint}/catalog/bundles/${options.bundleId}/versions`;
|
|
5468
6389
|
const versionUrlBase = `${options.adminEndpoint}/catalog/bundle-versions`;
|
|
5469
6390
|
function authHeaders() {
|
|
@@ -5471,11 +6392,18 @@ function useBundleVersions(options) {
|
|
|
5471
6392
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
5472
6393
|
}
|
|
5473
6394
|
async function fetchJson(url, init) {
|
|
6395
|
+
const method = init?.method ?? "GET";
|
|
5474
6396
|
const res = await http(url, {
|
|
5475
|
-
method
|
|
6397
|
+
method,
|
|
5476
6398
|
headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
|
|
5477
6399
|
body: init?.body
|
|
5478
6400
|
});
|
|
6401
|
+
requireServerAnswer(
|
|
6402
|
+
res.status,
|
|
6403
|
+
method,
|
|
6404
|
+
url,
|
|
6405
|
+
(diagnostic) => new BundlesApiError(res.status, null, diagnostic)
|
|
6406
|
+
);
|
|
5479
6407
|
if (res.status === 204) return null;
|
|
5480
6408
|
const body = await res.json().catch(() => null);
|
|
5481
6409
|
if (res.status >= 400) {
|
|
@@ -5504,7 +6432,8 @@ function useBundleVersions(options) {
|
|
|
5504
6432
|
method: "POST",
|
|
5505
6433
|
body: JSON.stringify(data)
|
|
5506
6434
|
});
|
|
5507
|
-
if (!result)
|
|
6435
|
+
if (!result)
|
|
6436
|
+
throw markEmptyResponse(new BundlesApiError(0, null, "CreateDraft returned no body"));
|
|
5508
6437
|
versions.value = [...versions.value, result.bundleVersion];
|
|
5509
6438
|
return result;
|
|
5510
6439
|
}
|
|
@@ -5513,7 +6442,8 @@ function useBundleVersions(options) {
|
|
|
5513
6442
|
`${versionUrlBase}/${versionId}`,
|
|
5514
6443
|
{ method: "PATCH", body: JSON.stringify(data) }
|
|
5515
6444
|
);
|
|
5516
|
-
if (!result)
|
|
6445
|
+
if (!result)
|
|
6446
|
+
throw markEmptyResponse(new BundlesApiError(0, null, "UpdateDraft returned no body"));
|
|
5517
6447
|
versions.value = versions.value.map((v) => v.id === versionId ? result.bundleVersion : v);
|
|
5518
6448
|
return result;
|
|
5519
6449
|
}
|
|
@@ -5522,7 +6452,8 @@ function useBundleVersions(options) {
|
|
|
5522
6452
|
`${versionUrlBase}/${versionId}/publish`,
|
|
5523
6453
|
{ method: "POST", body: JSON.stringify(opts) }
|
|
5524
6454
|
);
|
|
5525
|
-
if (!result)
|
|
6455
|
+
if (!result)
|
|
6456
|
+
throw markEmptyResponse(new BundlesApiError(0, null, "Publish returned no body"));
|
|
5526
6457
|
await load();
|
|
5527
6458
|
return result;
|
|
5528
6459
|
}
|
|
@@ -5537,7 +6468,7 @@ function useBundleVersions(options) {
|
|
|
5537
6468
|
}
|
|
5538
6469
|
|
|
5539
6470
|
// src/vue/use-bundle-versions-map.ts
|
|
5540
|
-
var
|
|
6471
|
+
var import_vue25 = require("vue");
|
|
5541
6472
|
function useBundleVersionsMap(options) {
|
|
5542
6473
|
if (!options?.adminEndpoint) {
|
|
5543
6474
|
throw new Error("useBundleVersionsMap: `adminEndpoint` is required.");
|
|
@@ -5546,9 +6477,9 @@ function useBundleVersionsMap(options) {
|
|
|
5546
6477
|
throw new Error("useBundleVersionsMap: `bundles` is required.");
|
|
5547
6478
|
}
|
|
5548
6479
|
const http = options.http ?? defaultHttpClient();
|
|
5549
|
-
const versionsByBundle = (0,
|
|
5550
|
-
const loading = (0,
|
|
5551
|
-
const error = (0,
|
|
6480
|
+
const versionsByBundle = (0, import_vue25.ref)({});
|
|
6481
|
+
const loading = (0, import_vue25.ref)(false);
|
|
6482
|
+
const error = (0, import_vue25.ref)(null);
|
|
5552
6483
|
function authHeaders() {
|
|
5553
6484
|
const token = options.getAuthToken?.();
|
|
5554
6485
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
@@ -5598,7 +6529,7 @@ function useBundleVersionsMap(options) {
|
|
|
5598
6529
|
console.warn(`useBundleVersionsMap: refreshOne('${bundleId}') failed`, err);
|
|
5599
6530
|
}
|
|
5600
6531
|
}
|
|
5601
|
-
(0,
|
|
6532
|
+
(0, import_vue25.watch)(
|
|
5602
6533
|
() => options.bundles.value.map((b) => b.id).join(","),
|
|
5603
6534
|
() => {
|
|
5604
6535
|
if (options.bundles.value.length > 0) void refresh();
|
|
@@ -5610,13 +6541,14 @@ function useBundleVersionsMap(options) {
|
|
|
5610
6541
|
}
|
|
5611
6542
|
|
|
5612
6543
|
// src/vue/use-tenant-subscription-bundles.ts
|
|
5613
|
-
var
|
|
6544
|
+
var import_vue26 = require("vue");
|
|
5614
6545
|
var TenantSubscriptionBundlesApiError = class extends Error {
|
|
5615
6546
|
constructor(status, body, message) {
|
|
5616
6547
|
super(message);
|
|
5617
6548
|
this.status = status;
|
|
5618
6549
|
this.body = body;
|
|
5619
6550
|
this.name = "TenantSubscriptionBundlesApiError";
|
|
6551
|
+
markPlatformError(this);
|
|
5620
6552
|
}
|
|
5621
6553
|
status;
|
|
5622
6554
|
body;
|
|
@@ -5629,19 +6561,26 @@ function useTenantSubscriptionBundles(options) {
|
|
|
5629
6561
|
}
|
|
5630
6562
|
const http = options.http ?? defaultHttpClient();
|
|
5631
6563
|
const baseUrl = `${options.billingEndpoint}/billing/subscription-bundles`;
|
|
5632
|
-
const bundles = (0,
|
|
5633
|
-
const loading = (0,
|
|
5634
|
-
const error = (0,
|
|
6564
|
+
const bundles = (0, import_vue26.ref)([]);
|
|
6565
|
+
const loading = (0, import_vue26.ref)(false);
|
|
6566
|
+
const error = (0, import_vue26.ref)(null);
|
|
5635
6567
|
function authHeaders() {
|
|
5636
6568
|
const token = options.getAuthToken?.();
|
|
5637
6569
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
5638
6570
|
}
|
|
5639
6571
|
async function fetchJson(url, init) {
|
|
6572
|
+
const method = init?.method ?? "GET";
|
|
5640
6573
|
const res = await http(url, {
|
|
5641
|
-
method
|
|
6574
|
+
method,
|
|
5642
6575
|
headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
|
|
5643
6576
|
body: init?.body
|
|
5644
6577
|
});
|
|
6578
|
+
requireServerAnswer(
|
|
6579
|
+
res.status,
|
|
6580
|
+
method,
|
|
6581
|
+
url,
|
|
6582
|
+
(diagnostic) => new TenantSubscriptionBundlesApiError(res.status, null, diagnostic)
|
|
6583
|
+
);
|
|
5645
6584
|
if (res.status === 204) return null;
|
|
5646
6585
|
const body = await res.json().catch(() => null);
|
|
5647
6586
|
if (res.status >= 400) {
|
|
@@ -5671,7 +6610,9 @@ function useTenantSubscriptionBundles(options) {
|
|
|
5671
6610
|
body: JSON.stringify(data)
|
|
5672
6611
|
});
|
|
5673
6612
|
if (!result) {
|
|
5674
|
-
throw
|
|
6613
|
+
throw markEmptyResponse(
|
|
6614
|
+
new TenantSubscriptionBundlesApiError(0, null, "add returned no body")
|
|
6615
|
+
);
|
|
5675
6616
|
}
|
|
5676
6617
|
const hydrated = rehydrateDates(result);
|
|
5677
6618
|
bundles.value = [hydrated, ...bundles.value];
|
|
@@ -5683,7 +6624,9 @@ function useTenantSubscriptionBundles(options) {
|
|
|
5683
6624
|
{ method: "DELETE", body: JSON.stringify(opts) }
|
|
5684
6625
|
);
|
|
5685
6626
|
if (!result) {
|
|
5686
|
-
throw
|
|
6627
|
+
throw markEmptyResponse(
|
|
6628
|
+
new TenantSubscriptionBundlesApiError(0, null, "cancel returned no body")
|
|
6629
|
+
);
|
|
5687
6630
|
}
|
|
5688
6631
|
const hydrated = rehydrateDates(result);
|
|
5689
6632
|
bundles.value = bundles.value.map((b) => b.id === subscriptionBundleId ? hydrated : b);
|
|
@@ -5705,13 +6648,14 @@ function rehydrateDates(raw) {
|
|
|
5705
6648
|
}
|
|
5706
6649
|
|
|
5707
6650
|
// src/vue/use-marketing-projections.ts
|
|
5708
|
-
var
|
|
6651
|
+
var import_vue27 = require("vue");
|
|
5709
6652
|
var MarketingProjectionsApiError = class extends Error {
|
|
5710
6653
|
constructor(status, body, message) {
|
|
5711
6654
|
super(message);
|
|
5712
6655
|
this.status = status;
|
|
5713
6656
|
this.body = body;
|
|
5714
6657
|
this.name = "MarketingProjectionsApiError";
|
|
6658
|
+
markPlatformError(this);
|
|
5715
6659
|
}
|
|
5716
6660
|
status;
|
|
5717
6661
|
body;
|
|
@@ -5723,12 +6667,11 @@ function useMarketingProjections(options) {
|
|
|
5723
6667
|
if (!options?.filter?.projectKey) {
|
|
5724
6668
|
throw new Error("useMarketingProjections: `filter.projectKey` is required.");
|
|
5725
6669
|
}
|
|
5726
|
-
const msg = useSaMessages("marketing");
|
|
5727
6670
|
const http = options.http ?? defaultHttpClient();
|
|
5728
|
-
const projections = (0,
|
|
5729
|
-
const filter = (0,
|
|
5730
|
-
const loading = (0,
|
|
5731
|
-
const error = (0,
|
|
6671
|
+
const projections = (0, import_vue27.ref)([]);
|
|
6672
|
+
const filter = (0, import_vue27.ref)({ ...options.filter });
|
|
6673
|
+
const loading = (0, import_vue27.ref)(false);
|
|
6674
|
+
const error = (0, import_vue27.ref)(null);
|
|
5732
6675
|
const baseUrl = `${options.adminEndpoint}/catalog/marketing-projections`;
|
|
5733
6676
|
function authHeaders() {
|
|
5734
6677
|
const token = options.getAuthToken?.();
|
|
@@ -5744,18 +6687,25 @@ function useMarketingProjections(options) {
|
|
|
5744
6687
|
return `${baseUrl}?${params.toString()}`;
|
|
5745
6688
|
}
|
|
5746
6689
|
async function fetchJson(url, init) {
|
|
6690
|
+
const method = init?.method ?? "GET";
|
|
5747
6691
|
const res = await http(url, {
|
|
5748
|
-
method
|
|
6692
|
+
method,
|
|
5749
6693
|
headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
|
|
5750
6694
|
body: init?.body
|
|
5751
6695
|
});
|
|
6696
|
+
requireServerAnswer(
|
|
6697
|
+
res.status,
|
|
6698
|
+
method,
|
|
6699
|
+
url,
|
|
6700
|
+
(diagnostic) => new MarketingProjectionsApiError(res.status, null, diagnostic)
|
|
6701
|
+
);
|
|
5752
6702
|
if (res.status === 204) return null;
|
|
5753
6703
|
const body = await res.json().catch(() => null);
|
|
5754
6704
|
if (res.status >= 400) {
|
|
5755
6705
|
throw new MarketingProjectionsApiError(
|
|
5756
6706
|
res.status,
|
|
5757
6707
|
body,
|
|
5758
|
-
|
|
6708
|
+
`Marketing projections API responded with HTTP ${res.status}`
|
|
5759
6709
|
);
|
|
5760
6710
|
}
|
|
5761
6711
|
return body;
|
|
@@ -5782,7 +6732,9 @@ function useMarketingProjections(options) {
|
|
|
5782
6732
|
body: JSON.stringify(data)
|
|
5783
6733
|
});
|
|
5784
6734
|
if (!created) {
|
|
5785
|
-
throw
|
|
6735
|
+
throw markEmptyResponse(
|
|
6736
|
+
new MarketingProjectionsApiError(0, null, "Create returned no body")
|
|
6737
|
+
);
|
|
5786
6738
|
}
|
|
5787
6739
|
await load();
|
|
5788
6740
|
return created;
|
|
@@ -5793,7 +6745,9 @@ function useMarketingProjections(options) {
|
|
|
5793
6745
|
body: JSON.stringify(data)
|
|
5794
6746
|
});
|
|
5795
6747
|
if (!updated) {
|
|
5796
|
-
throw
|
|
6748
|
+
throw markEmptyResponse(
|
|
6749
|
+
new MarketingProjectionsApiError(0, null, "Update returned no body")
|
|
6750
|
+
);
|
|
5797
6751
|
}
|
|
5798
6752
|
projections.value = projections.value.map((p) => p.id === id ? updated : p);
|
|
5799
6753
|
return updated;
|
|
@@ -5809,13 +6763,14 @@ function useMarketingProjections(options) {
|
|
|
5809
6763
|
}
|
|
5810
6764
|
|
|
5811
6765
|
// src/vue/use-promotions.ts
|
|
5812
|
-
var
|
|
6766
|
+
var import_vue28 = require("vue");
|
|
5813
6767
|
var PromotionsApiError = class extends Error {
|
|
5814
6768
|
constructor(status, body, message) {
|
|
5815
6769
|
super(message);
|
|
5816
6770
|
this.status = status;
|
|
5817
6771
|
this.body = body;
|
|
5818
6772
|
this.name = "PromotionsApiError";
|
|
6773
|
+
markPlatformError(this);
|
|
5819
6774
|
}
|
|
5820
6775
|
status;
|
|
5821
6776
|
body;
|
|
@@ -5827,11 +6782,10 @@ function usePromotions(options) {
|
|
|
5827
6782
|
if (!options?.projectKey) {
|
|
5828
6783
|
throw new Error("usePromotions: `projectKey` is required.");
|
|
5829
6784
|
}
|
|
5830
|
-
const msg = useSaMessages("promos");
|
|
5831
6785
|
const http = options.http ?? defaultHttpClient();
|
|
5832
|
-
const promotions = (0,
|
|
5833
|
-
const loading = (0,
|
|
5834
|
-
const error = (0,
|
|
6786
|
+
const promotions = (0, import_vue28.ref)([]);
|
|
6787
|
+
const loading = (0, import_vue28.ref)(false);
|
|
6788
|
+
const error = (0, import_vue28.ref)(null);
|
|
5835
6789
|
const baseUrl = `${options.adminEndpoint}/catalog/promotions`;
|
|
5836
6790
|
const pk = encodeURIComponent(options.projectKey);
|
|
5837
6791
|
function authHeaders() {
|
|
@@ -5839,18 +6793,25 @@ function usePromotions(options) {
|
|
|
5839
6793
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
5840
6794
|
}
|
|
5841
6795
|
async function fetchJson(url, init) {
|
|
6796
|
+
const method = init?.method ?? "GET";
|
|
5842
6797
|
const res = await http(url, {
|
|
5843
|
-
method
|
|
6798
|
+
method,
|
|
5844
6799
|
headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
|
|
5845
6800
|
body: init?.body
|
|
5846
6801
|
});
|
|
6802
|
+
requireServerAnswer(
|
|
6803
|
+
res.status,
|
|
6804
|
+
method,
|
|
6805
|
+
url,
|
|
6806
|
+
(diagnostic) => new PromotionsApiError(res.status, null, diagnostic)
|
|
6807
|
+
);
|
|
5847
6808
|
if (res.status === 204) return null;
|
|
5848
6809
|
const body = await res.json().catch(() => null);
|
|
5849
6810
|
if (res.status >= 400) {
|
|
5850
6811
|
throw new PromotionsApiError(
|
|
5851
6812
|
res.status,
|
|
5852
6813
|
body,
|
|
5853
|
-
|
|
6814
|
+
`Promotions API responded with HTTP ${res.status}`
|
|
5854
6815
|
);
|
|
5855
6816
|
}
|
|
5856
6817
|
return body;
|
|
@@ -5872,7 +6833,8 @@ function usePromotions(options) {
|
|
|
5872
6833
|
method: "POST",
|
|
5873
6834
|
body: JSON.stringify(data)
|
|
5874
6835
|
});
|
|
5875
|
-
if (!created)
|
|
6836
|
+
if (!created)
|
|
6837
|
+
throw markEmptyResponse(new PromotionsApiError(0, null, "Create returned no body"));
|
|
5876
6838
|
promotions.value = [...promotions.value, created];
|
|
5877
6839
|
return created;
|
|
5878
6840
|
}
|
|
@@ -5881,7 +6843,8 @@ function usePromotions(options) {
|
|
|
5881
6843
|
method: "PATCH",
|
|
5882
6844
|
body: JSON.stringify(data)
|
|
5883
6845
|
});
|
|
5884
|
-
if (!updated)
|
|
6846
|
+
if (!updated)
|
|
6847
|
+
throw markEmptyResponse(new PromotionsApiError(0, null, "Update returned no body"));
|
|
5885
6848
|
promotions.value = promotions.value.map((p) => p.id === id ? updated : p);
|
|
5886
6849
|
return updated;
|
|
5887
6850
|
}
|
|
@@ -5896,13 +6859,14 @@ function usePromotions(options) {
|
|
|
5896
6859
|
}
|
|
5897
6860
|
|
|
5898
6861
|
// src/vue/use-plans.ts
|
|
5899
|
-
var
|
|
6862
|
+
var import_vue29 = require("vue");
|
|
5900
6863
|
var PlansApiError = class extends Error {
|
|
5901
6864
|
constructor(status, body, message) {
|
|
5902
6865
|
super(message);
|
|
5903
6866
|
this.status = status;
|
|
5904
6867
|
this.body = body;
|
|
5905
6868
|
this.name = "PlansApiError";
|
|
6869
|
+
markPlatformError(this);
|
|
5906
6870
|
}
|
|
5907
6871
|
status;
|
|
5908
6872
|
body;
|
|
@@ -5917,18 +6881,19 @@ function usePlans(options) {
|
|
|
5917
6881
|
throw new Error("usePlans: `projectKey` is required.");
|
|
5918
6882
|
}
|
|
5919
6883
|
const http = options.http ?? defaultHttpClient();
|
|
5920
|
-
const plans = (0,
|
|
5921
|
-
const loading = (0,
|
|
5922
|
-
const error = (0,
|
|
5923
|
-
const tenantCountsByPlanKey = (0,
|
|
6884
|
+
const plans = (0, import_vue29.ref)([]);
|
|
6885
|
+
const loading = (0, import_vue29.ref)(false);
|
|
6886
|
+
const error = (0, import_vue29.ref)(null);
|
|
6887
|
+
const tenantCountsByPlanKey = (0, import_vue29.ref)({});
|
|
5924
6888
|
const baseUrl = `${options.adminEndpoint}/catalog/plans`;
|
|
5925
6889
|
function authHeaders() {
|
|
5926
6890
|
const token = options.getAuthToken?.();
|
|
5927
6891
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
5928
6892
|
}
|
|
5929
6893
|
async function fetchJson(url, init) {
|
|
6894
|
+
const method = init?.method ?? "GET";
|
|
5930
6895
|
const res = await http(url, {
|
|
5931
|
-
method
|
|
6896
|
+
method,
|
|
5932
6897
|
headers: {
|
|
5933
6898
|
"content-type": "application/json",
|
|
5934
6899
|
...authHeaders(),
|
|
@@ -5936,6 +6901,12 @@ function usePlans(options) {
|
|
|
5936
6901
|
},
|
|
5937
6902
|
body: init?.body
|
|
5938
6903
|
});
|
|
6904
|
+
requireServerAnswer(
|
|
6905
|
+
res.status,
|
|
6906
|
+
method,
|
|
6907
|
+
url,
|
|
6908
|
+
(diagnostic) => new PlansApiError(res.status, null, diagnostic)
|
|
6909
|
+
);
|
|
5939
6910
|
if (res.status === 204) return null;
|
|
5940
6911
|
const body = await res.json().catch(() => null);
|
|
5941
6912
|
if (res.status >= 400) {
|
|
@@ -5976,7 +6947,8 @@ function usePlans(options) {
|
|
|
5976
6947
|
method: "POST",
|
|
5977
6948
|
body: JSON.stringify(data)
|
|
5978
6949
|
});
|
|
5979
|
-
if (!created)
|
|
6950
|
+
if (!created)
|
|
6951
|
+
throw markEmptyResponse(new PlansApiError(0, null, "Create returned no body"));
|
|
5980
6952
|
plans.value = [...plans.value, created];
|
|
5981
6953
|
return created;
|
|
5982
6954
|
}
|
|
@@ -5985,7 +6957,8 @@ function usePlans(options) {
|
|
|
5985
6957
|
method: "PATCH",
|
|
5986
6958
|
body: JSON.stringify(data)
|
|
5987
6959
|
});
|
|
5988
|
-
if (!updated)
|
|
6960
|
+
if (!updated)
|
|
6961
|
+
throw markEmptyResponse(new PlansApiError(0, null, "Update returned no body"));
|
|
5989
6962
|
plans.value = plans.value.map((p) => p.id === planId ? updated : p);
|
|
5990
6963
|
return updated;
|
|
5991
6964
|
}
|
|
@@ -6021,9 +6994,9 @@ function usePlanVersions(options) {
|
|
|
6021
6994
|
throw new Error("usePlanVersions: `planId` is required.");
|
|
6022
6995
|
}
|
|
6023
6996
|
const http = options.http ?? defaultHttpClient();
|
|
6024
|
-
const versions = (0,
|
|
6025
|
-
const loading = (0,
|
|
6026
|
-
const error = (0,
|
|
6997
|
+
const versions = (0, import_vue29.ref)([]);
|
|
6998
|
+
const loading = (0, import_vue29.ref)(false);
|
|
6999
|
+
const error = (0, import_vue29.ref)(null);
|
|
6027
7000
|
const planVersionsUrl = `${options.adminEndpoint}/catalog/plans/${options.planId}/versions`;
|
|
6028
7001
|
const versionUrlBase = `${options.adminEndpoint}/catalog/plan-versions`;
|
|
6029
7002
|
function authHeaders() {
|
|
@@ -6031,8 +7004,9 @@ function usePlanVersions(options) {
|
|
|
6031
7004
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
6032
7005
|
}
|
|
6033
7006
|
async function fetchJson(url, init) {
|
|
7007
|
+
const method = init?.method ?? "GET";
|
|
6034
7008
|
const res = await http(url, {
|
|
6035
|
-
method
|
|
7009
|
+
method,
|
|
6036
7010
|
headers: {
|
|
6037
7011
|
"content-type": "application/json",
|
|
6038
7012
|
...authHeaders(),
|
|
@@ -6040,6 +7014,12 @@ function usePlanVersions(options) {
|
|
|
6040
7014
|
},
|
|
6041
7015
|
body: init?.body
|
|
6042
7016
|
});
|
|
7017
|
+
requireServerAnswer(
|
|
7018
|
+
res.status,
|
|
7019
|
+
method,
|
|
7020
|
+
url,
|
|
7021
|
+
(diagnostic) => new PlansApiError(res.status, null, diagnostic)
|
|
7022
|
+
);
|
|
6043
7023
|
if (res.status === 204) return null;
|
|
6044
7024
|
const body = await res.json().catch(() => null);
|
|
6045
7025
|
if (res.status >= 400) {
|
|
@@ -6068,7 +7048,8 @@ function usePlanVersions(options) {
|
|
|
6068
7048
|
method: "POST",
|
|
6069
7049
|
body: JSON.stringify(data)
|
|
6070
7050
|
});
|
|
6071
|
-
if (!result)
|
|
7051
|
+
if (!result)
|
|
7052
|
+
throw markEmptyResponse(new PlansApiError(0, null, "CreateDraft returned no body"));
|
|
6072
7053
|
versions.value = [...versions.value, result.planVersion];
|
|
6073
7054
|
return result;
|
|
6074
7055
|
}
|
|
@@ -6077,7 +7058,8 @@ function usePlanVersions(options) {
|
|
|
6077
7058
|
`${versionUrlBase}/${versionId}`,
|
|
6078
7059
|
{ method: "PATCH", body: JSON.stringify(data) }
|
|
6079
7060
|
);
|
|
6080
|
-
if (!result)
|
|
7061
|
+
if (!result)
|
|
7062
|
+
throw markEmptyResponse(new PlansApiError(0, null, "UpdateDraft returned no body"));
|
|
6081
7063
|
versions.value = versions.value.map((v) => v.id === versionId ? result.planVersion : v);
|
|
6082
7064
|
return result;
|
|
6083
7065
|
}
|
|
@@ -6086,7 +7068,8 @@ function usePlanVersions(options) {
|
|
|
6086
7068
|
`${versionUrlBase}/${versionId}/publish`,
|
|
6087
7069
|
{ method: "POST", body: JSON.stringify(opts) }
|
|
6088
7070
|
);
|
|
6089
|
-
if (!result)
|
|
7071
|
+
if (!result)
|
|
7072
|
+
throw markEmptyResponse(new PlansApiError(0, null, "Publish returned no body"));
|
|
6090
7073
|
versions.value = versions.value.map((v) => v.id === versionId ? result.planVersion : v);
|
|
6091
7074
|
return result;
|
|
6092
7075
|
}
|
|
@@ -6099,7 +7082,8 @@ function usePlanVersions(options) {
|
|
|
6099
7082
|
`${versionUrlBase}/${versionId}/terminate`,
|
|
6100
7083
|
{ method: "POST", body: JSON.stringify({ endsAt }) }
|
|
6101
7084
|
);
|
|
6102
|
-
if (!updated)
|
|
7085
|
+
if (!updated)
|
|
7086
|
+
throw markEmptyResponse(new PlansApiError(0, null, "Terminate returned no body"));
|
|
6103
7087
|
versions.value = versions.value.map((v) => v.id === versionId ? updated : v);
|
|
6104
7088
|
return updated;
|
|
6105
7089
|
}
|
|
@@ -6120,7 +7104,7 @@ function usePlanVersions(options) {
|
|
|
6120
7104
|
}
|
|
6121
7105
|
|
|
6122
7106
|
// src/vue/use-live-plan-versions.ts
|
|
6123
|
-
var
|
|
7107
|
+
var import_vue30 = require("vue");
|
|
6124
7108
|
function useLivePlanVersions(options) {
|
|
6125
7109
|
if (!options?.adminEndpoint) {
|
|
6126
7110
|
throw new Error("useLivePlanVersions: `adminEndpoint` is required.");
|
|
@@ -6129,9 +7113,9 @@ function useLivePlanVersions(options) {
|
|
|
6129
7113
|
throw new Error("useLivePlanVersions: `plans` is required.");
|
|
6130
7114
|
}
|
|
6131
7115
|
const http = options.http ?? defaultHttpClient();
|
|
6132
|
-
const livePlanVersions = (0,
|
|
6133
|
-
const loading = (0,
|
|
6134
|
-
const error = (0,
|
|
7116
|
+
const livePlanVersions = (0, import_vue30.ref)({});
|
|
7117
|
+
const loading = (0, import_vue30.ref)(false);
|
|
7118
|
+
const error = (0, import_vue30.ref)(null);
|
|
6135
7119
|
function authHeaders() {
|
|
6136
7120
|
const token = options.getAuthToken?.();
|
|
6137
7121
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
@@ -6173,7 +7157,7 @@ function useLivePlanVersions(options) {
|
|
|
6173
7157
|
loading.value = false;
|
|
6174
7158
|
}
|
|
6175
7159
|
}
|
|
6176
|
-
(0,
|
|
7160
|
+
(0, import_vue30.watch)(
|
|
6177
7161
|
() => options.plans.value.map((p) => p.id).join(","),
|
|
6178
7162
|
() => {
|
|
6179
7163
|
if (options.plans.value.length > 0) void refresh();
|
|
@@ -6195,12 +7179,12 @@ function findLatestLive(versions) {
|
|
|
6195
7179
|
}
|
|
6196
7180
|
|
|
6197
7181
|
// src/vue/use-manifest.ts
|
|
6198
|
-
var
|
|
7182
|
+
var import_vue31 = require("vue");
|
|
6199
7183
|
function useManifest(options) {
|
|
6200
7184
|
const loader = new ManifestLoader(options);
|
|
6201
|
-
const manifest = (0,
|
|
6202
|
-
const loading = (0,
|
|
6203
|
-
const error = (0,
|
|
7185
|
+
const manifest = (0, import_vue31.ref)(null);
|
|
7186
|
+
const loading = (0, import_vue31.ref)(false);
|
|
7187
|
+
const error = (0, import_vue31.ref)(null);
|
|
6204
7188
|
async function load() {
|
|
6205
7189
|
loading.value = true;
|
|
6206
7190
|
error.value = null;
|
|
@@ -6226,24 +7210,24 @@ function useManifest(options) {
|
|
|
6226
7210
|
}
|
|
6227
7211
|
|
|
6228
7212
|
// src/vue/use-nav.ts
|
|
6229
|
-
var
|
|
7213
|
+
var import_vue32 = require("vue");
|
|
6230
7214
|
function useNav(manifest, options = {}) {
|
|
6231
7215
|
const { locale } = useSuperAdminI18n();
|
|
6232
|
-
const activeLocale = (0,
|
|
6233
|
-
const routes = (0,
|
|
7216
|
+
const activeLocale = (0, import_vue32.computed)(() => options.locale ?? locale.value);
|
|
7217
|
+
const routes = (0, import_vue32.computed)(() => {
|
|
6234
7218
|
if (!manifest.value) return [];
|
|
6235
7219
|
return buildRoutes(manifest.value, { ...options, locale: activeLocale.value });
|
|
6236
7220
|
});
|
|
6237
|
-
const sidebar = (0,
|
|
7221
|
+
const sidebar = (0, import_vue32.computed)(
|
|
6238
7222
|
() => buildSidebar(routes.value, defaultSectionOrder(activeLocale.value))
|
|
6239
7223
|
);
|
|
6240
7224
|
return { routes, sidebar };
|
|
6241
7225
|
}
|
|
6242
7226
|
|
|
6243
7227
|
// src/vue/use-actions.ts
|
|
6244
|
-
var
|
|
7228
|
+
var import_vue33 = require("vue");
|
|
6245
7229
|
function useActions(manifest, actions) {
|
|
6246
|
-
const registry = (0,
|
|
7230
|
+
const registry = (0, import_vue33.computed)(() => {
|
|
6247
7231
|
if (!manifest.value) return null;
|
|
6248
7232
|
return new ActionRegistry(manifest.value, actions);
|
|
6249
7233
|
});
|
|
@@ -6251,18 +7235,18 @@ function useActions(manifest, actions) {
|
|
|
6251
7235
|
}
|
|
6252
7236
|
|
|
6253
7237
|
// src/vue/use-tenant-action-flow.ts
|
|
6254
|
-
var
|
|
7238
|
+
var import_vue34 = require("vue");
|
|
6255
7239
|
function useTenantActionFlow(manifest, providers = {}) {
|
|
6256
7240
|
const handlers = useSuperAdminActions();
|
|
6257
7241
|
const msg = useSaMessages("tenants");
|
|
6258
|
-
const registry = (0,
|
|
7242
|
+
const registry = (0, import_vue34.computed)(() => {
|
|
6259
7243
|
if (!manifest.value) return null;
|
|
6260
7244
|
return new ActionRegistry(manifest.value, handlers);
|
|
6261
7245
|
});
|
|
6262
|
-
const orphanedDefs = (0,
|
|
7246
|
+
const orphanedDefs = (0, import_vue34.computed)(
|
|
6263
7247
|
() => registry.value ? registry.value.listOrphanedDefs() : []
|
|
6264
7248
|
);
|
|
6265
|
-
const availableActions = (0,
|
|
7249
|
+
const availableActions = (0, import_vue34.computed)(() => {
|
|
6266
7250
|
const m = manifest.value;
|
|
6267
7251
|
const reg = registry.value;
|
|
6268
7252
|
if (!m || !reg) return [];
|
|
@@ -6335,7 +7319,7 @@ async function runFlow(def, row, registry, providers, successTemplate) {
|
|
|
6335
7319
|
}
|
|
6336
7320
|
|
|
6337
7321
|
// src/vue/use-platform-tenant-actions.ts
|
|
6338
|
-
var
|
|
7322
|
+
var import_vue35 = require("vue");
|
|
6339
7323
|
var DEFAULT_VISIBLE_FOR_ROW = (def, row) => {
|
|
6340
7324
|
if (def.actionKey === "tenants.suspend") return row.isActive;
|
|
6341
7325
|
if (def.actionKey === "tenants.reactivate") return !row.isActive;
|
|
@@ -6371,7 +7355,7 @@ function usePlatformTenantActions(options) {
|
|
|
6371
7355
|
const iconForActionKey = options.iconForActionKey ?? DEFAULT_ICON_FOR_ACTION_KEY;
|
|
6372
7356
|
const toneForActionKey = options.toneForActionKey ?? DEFAULT_TONE_FOR_ACTION_KEY;
|
|
6373
7357
|
const visibleForRow = options.visibleForRow ?? DEFAULT_VISIBLE_FOR_ROW;
|
|
6374
|
-
const mfa = (0,
|
|
7358
|
+
const mfa = (0, import_vue35.ref)({ show: false, description: "", error: "" });
|
|
6375
7359
|
let pendingMfaResolve = null;
|
|
6376
7360
|
function showMfaDialog(def, ctx) {
|
|
6377
7361
|
return new Promise((resolve) => {
|
|
@@ -6396,7 +7380,7 @@ function usePlatformTenantActions(options) {
|
|
|
6396
7380
|
pendingMfaResolve(null);
|
|
6397
7381
|
}
|
|
6398
7382
|
}
|
|
6399
|
-
const confirmDialog = (0,
|
|
7383
|
+
const confirmDialog = (0, import_vue35.ref)({
|
|
6400
7384
|
show: false,
|
|
6401
7385
|
def: null,
|
|
6402
7386
|
row: null
|
|
@@ -6438,7 +7422,7 @@ function usePlatformTenantActions(options) {
|
|
|
6438
7422
|
},
|
|
6439
7423
|
visibleForRow
|
|
6440
7424
|
});
|
|
6441
|
-
const manifestActions = (0,
|
|
7425
|
+
const manifestActions = (0, import_vue35.computed)(
|
|
6442
7426
|
() => flow.availableActions.value.map((def) => ({
|
|
6443
7427
|
id: def.id,
|
|
6444
7428
|
label: def.label,
|
|
@@ -6452,7 +7436,7 @@ function usePlatformTenantActions(options) {
|
|
|
6452
7436
|
}
|
|
6453
7437
|
}))
|
|
6454
7438
|
);
|
|
6455
|
-
const realOrphans = (0,
|
|
7439
|
+
const realOrphans = (0, import_vue35.computed)(() => {
|
|
6456
7440
|
const caps = options.manifest.value?.capabilities ?? {};
|
|
6457
7441
|
const defs = options.manifest.value?.tenants?.actions ?? [];
|
|
6458
7442
|
const defByActionKey = new Map(defs.map((d) => [d.actionKey, d]));
|
|
@@ -6477,12 +7461,12 @@ function usePlatformTenantActions(options) {
|
|
|
6477
7461
|
}
|
|
6478
7462
|
|
|
6479
7463
|
// src/vue/use-batch-columns.ts
|
|
6480
|
-
var
|
|
7464
|
+
var import_vue36 = require("vue");
|
|
6481
7465
|
function useBatchColumns(manifest, tenantIds, options = {}) {
|
|
6482
7466
|
const fetcher = new BatchColumnFetcher(options);
|
|
6483
|
-
const data = (0,
|
|
6484
|
-
const loading = (0,
|
|
6485
|
-
const error = (0,
|
|
7467
|
+
const data = (0, import_vue36.ref)({});
|
|
7468
|
+
const loading = (0, import_vue36.ref)(false);
|
|
7469
|
+
const error = (0, import_vue36.ref)(null);
|
|
6486
7470
|
async function load() {
|
|
6487
7471
|
if (!manifest.value || tenantIds.value.length === 0) {
|
|
6488
7472
|
data.value = {};
|
|
@@ -6499,22 +7483,23 @@ function useBatchColumns(manifest, tenantIds, options = {}) {
|
|
|
6499
7483
|
loading.value = false;
|
|
6500
7484
|
}
|
|
6501
7485
|
}
|
|
6502
|
-
(0,
|
|
7486
|
+
(0, import_vue36.watch)([manifest, tenantIds], () => {
|
|
6503
7487
|
void load();
|
|
6504
7488
|
});
|
|
6505
7489
|
return { data, loading, error, reload: load };
|
|
6506
7490
|
}
|
|
6507
7491
|
|
|
6508
7492
|
// src/vue/platform-loaders.ts
|
|
6509
|
-
function
|
|
7493
|
+
function resolveSuperAdminEndpoints(endpoints) {
|
|
6510
7494
|
return {
|
|
6511
7495
|
apiBase: endpoints.apiBase,
|
|
6512
7496
|
publicBootEndpoint: endpoints.publicBootEndpoint ?? `${endpoints.apiBase}/boot`,
|
|
6513
|
-
manifestEndpoint: endpoints.manifestEndpoint ?? `${endpoints.apiBase}/manifest
|
|
7497
|
+
manifestEndpoint: endpoints.manifestEndpoint ?? `${endpoints.apiBase}/manifest`,
|
|
7498
|
+
projectKey: endpoints.projectKey ?? ""
|
|
6514
7499
|
};
|
|
6515
7500
|
}
|
|
6516
7501
|
function createPlatformLoaders(options) {
|
|
6517
|
-
const resolved =
|
|
7502
|
+
const resolved = resolveSuperAdminEndpoints(options.endpoints);
|
|
6518
7503
|
const bootLoader = new BootLoader({
|
|
6519
7504
|
endpoint: resolved.publicBootEndpoint,
|
|
6520
7505
|
http: options.http
|
|
@@ -6531,13 +7516,13 @@ function createPlatformLoaders(options) {
|
|
|
6531
7516
|
|
|
6532
7517
|
// src/vue/manifest-store-factory.ts
|
|
6533
7518
|
var import_pinia = require("pinia");
|
|
6534
|
-
var
|
|
7519
|
+
var import_vue37 = require("vue");
|
|
6535
7520
|
function createManifestStore(options) {
|
|
6536
7521
|
return (0, import_pinia.defineStore)(options.id ?? "admin-manifest", () => {
|
|
6537
|
-
const manifest = (0,
|
|
6538
|
-
const loading = (0,
|
|
6539
|
-
const error = (0,
|
|
6540
|
-
const loaded = (0,
|
|
7522
|
+
const manifest = (0, import_vue37.ref)(null);
|
|
7523
|
+
const loading = (0, import_vue37.ref)(false);
|
|
7524
|
+
const error = (0, import_vue37.ref)(null);
|
|
7525
|
+
const loaded = (0, import_vue37.ref)(false);
|
|
6541
7526
|
let inflight = null;
|
|
6542
7527
|
async function load() {
|
|
6543
7528
|
loading.value = true;
|
|
@@ -6970,6 +7955,7 @@ function planChangeWizardI18n(i18n) {
|
|
|
6970
7955
|
ALL_ROWS,
|
|
6971
7956
|
ActionDefNotInManifestError,
|
|
6972
7957
|
ActionRegistry,
|
|
7958
|
+
AdminError,
|
|
6973
7959
|
BatchColumnDriftError,
|
|
6974
7960
|
BatchColumnFetcher,
|
|
6975
7961
|
BootLoadError,
|
|
@@ -6990,6 +7976,10 @@ function planChangeWizardI18n(i18n) {
|
|
|
6990
7976
|
IDENTITY_ACCENT_VALUES,
|
|
6991
7977
|
IDENTITY_NEUTRAL,
|
|
6992
7978
|
IDENTITY_NEUTRAL_VALUE,
|
|
7979
|
+
LIST_FIRST_PAGE,
|
|
7980
|
+
LIST_PAGE_SIZE_DEFAULT,
|
|
7981
|
+
LIST_PAGE_SIZE_MAX,
|
|
7982
|
+
LIST_PAGINATION_PARAMS,
|
|
6993
7983
|
ManifestLoadError,
|
|
6994
7984
|
ManifestLoader,
|
|
6995
7985
|
MarketingProjectionsApiError,
|
|
@@ -6999,15 +7989,18 @@ function planChangeWizardI18n(i18n) {
|
|
|
6999
7989
|
PlansApiError,
|
|
7000
7990
|
ProjectPageHost,
|
|
7001
7991
|
PromotionsApiError,
|
|
7992
|
+
SA_COLOR_SCHEMES,
|
|
7002
7993
|
SA_INTL_LOCALES,
|
|
7003
7994
|
SA_LOCALES,
|
|
7004
7995
|
SA_LOCALE_LABELS,
|
|
7005
7996
|
SA_LOCALE_STORAGE_KEY,
|
|
7006
7997
|
SA_MESSAGES,
|
|
7007
7998
|
SA_THEME_KEY,
|
|
7999
|
+
SA_THEME_STORAGE_KEY,
|
|
7008
8000
|
SERVER_PAGE_SIZE_OPTIONS,
|
|
7009
8001
|
SUPER_ADMIN_ACTIONS_KEY,
|
|
7010
8002
|
SUPER_ADMIN_BRAND_KEY,
|
|
8003
|
+
SUPER_ADMIN_CONFIRM_KEY,
|
|
7011
8004
|
SUPER_ADMIN_ENDPOINTS_KEY,
|
|
7012
8005
|
SUPER_ADMIN_EXTENSIONS_KEY,
|
|
7013
8006
|
SUPER_ADMIN_HTTP_KEY,
|
|
@@ -7016,19 +8009,28 @@ function planChangeWizardI18n(i18n) {
|
|
|
7016
8009
|
SUPER_ADMIN_MANIFEST_CLEAR_CACHE_KEY,
|
|
7017
8010
|
SUPER_ADMIN_MANIFEST_KEY,
|
|
7018
8011
|
SUPER_ADMIN_NOTIFY_KEY,
|
|
8012
|
+
SUPER_ADMIN_RESOURCES_KEY,
|
|
7019
8013
|
TenantSubscriptionBundlesApiError,
|
|
8014
|
+
adminErrorMessage,
|
|
8015
|
+
auditResource,
|
|
8016
|
+
bindResource,
|
|
7020
8017
|
buildFeatureRegistry,
|
|
7021
8018
|
buildFeatureRouterGuard,
|
|
7022
8019
|
buildNavigationGuard,
|
|
7023
8020
|
buildQuotaRegistry,
|
|
7024
8021
|
buildRoutes,
|
|
7025
8022
|
buildSidebar,
|
|
8023
|
+
clampListPage,
|
|
8024
|
+
clampListPageSize,
|
|
7026
8025
|
countPlans,
|
|
7027
8026
|
createAdminResourceClient,
|
|
7028
8027
|
createAdminRoutes,
|
|
8028
|
+
createAxiosHttpClient,
|
|
8029
|
+
createFetchHttpClient,
|
|
7029
8030
|
createManifestStore,
|
|
7030
8031
|
createPlatformLoaders,
|
|
7031
8032
|
createProjectPageHostRoute,
|
|
8033
|
+
createResourceRegistry,
|
|
7032
8034
|
createSaCatalog,
|
|
7033
8035
|
createSaTheme,
|
|
7034
8036
|
createSuperAdminI18n,
|
|
@@ -7038,30 +8040,57 @@ function planChangeWizardI18n(i18n) {
|
|
|
7038
8040
|
defaultSectionOrder,
|
|
7039
8041
|
defaultTenantPlanSectionI18n,
|
|
7040
8042
|
defaultToneForActionKey,
|
|
8043
|
+
defineListOp,
|
|
7041
8044
|
defineMessages,
|
|
8045
|
+
defineResource,
|
|
8046
|
+
filterQueryString,
|
|
7042
8047
|
formatCurrency,
|
|
7043
8048
|
formatMessage,
|
|
7044
8049
|
getJson,
|
|
7045
8050
|
identityAccentAt,
|
|
7046
8051
|
identityAccentFor,
|
|
7047
8052
|
identityChipStyle,
|
|
8053
|
+
isAdminError,
|
|
8054
|
+
isAxiosNoResponseError,
|
|
7048
8055
|
isCurrentlyValid,
|
|
8056
|
+
isEmptyResponse,
|
|
7049
8057
|
isExpired,
|
|
7050
8058
|
isFutureScheduled,
|
|
8059
|
+
isPlatformError,
|
|
7051
8060
|
isProductionBoot,
|
|
7052
8061
|
isSaBuiltinLocale,
|
|
8062
|
+
isSentInQuery,
|
|
8063
|
+
isTransportFailure,
|
|
8064
|
+
listUrl,
|
|
8065
|
+
markEmptyResponse,
|
|
8066
|
+
markPlatformError,
|
|
8067
|
+
markTransportFailure,
|
|
7053
8068
|
mergeMessages,
|
|
7054
8069
|
planChangeWizardI18n,
|
|
8070
|
+
planVersionsResource,
|
|
8071
|
+
plansResource,
|
|
8072
|
+
platformResources,
|
|
7055
8073
|
postJson,
|
|
7056
8074
|
provideEntitlement,
|
|
8075
|
+
readErrorCode,
|
|
8076
|
+
readErrorDetail,
|
|
8077
|
+
readListPage,
|
|
8078
|
+
requestJson,
|
|
8079
|
+
requestJsonBody,
|
|
8080
|
+
requireServerAnswer,
|
|
7057
8081
|
resolveExtension,
|
|
7058
8082
|
resolveLoginBranding,
|
|
7059
8083
|
resolveMessages,
|
|
7060
8084
|
resolvePlans,
|
|
8085
|
+
resolveSuperAdminEndpoints,
|
|
8086
|
+
tenantsResource,
|
|
8087
|
+
toAdminError,
|
|
7061
8088
|
todayIsoDate,
|
|
7062
8089
|
trimTrailingSlashes,
|
|
7063
8090
|
useActions,
|
|
7064
8091
|
useApiList,
|
|
8092
|
+
useAsyncAction,
|
|
8093
|
+
useAsyncData,
|
|
7065
8094
|
useAuditEntries,
|
|
7066
8095
|
useBatchColumns,
|
|
7067
8096
|
useBulkPublish,
|
|
@@ -7083,6 +8112,8 @@ function planChangeWizardI18n(i18n) {
|
|
|
7083
8112
|
usePlatformTenantActions,
|
|
7084
8113
|
usePromotions,
|
|
7085
8114
|
usePublicBoot,
|
|
8115
|
+
useResource,
|
|
8116
|
+
useResourceList,
|
|
7086
8117
|
useSaMessages,
|
|
7087
8118
|
useSaTheme,
|
|
7088
8119
|
useSubscriptionDraft,
|