@saasicat/ui-vue 0.25.0 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -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-COVQLDBZ.js} +759 -24
- package/dist/{chunk-TQQMKWN6.js → chunk-DT25QAU2.js} +119 -7
- package/dist/{chunk-D74FFCCP.js → chunk-O3Y3BYYS.js} +96 -178
- package/dist/client/index.cjs +770 -84
- package/dist/client/index.d.cts +429 -9
- package/dist/client/index.d.ts +429 -9
- package/dist/client/index.js +84 -12
- package/dist/index.cjs +1337 -295
- 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 +585 -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 +6 -4
- package/src/client/admin-error.ts +569 -0
- package/src/client/admin-resource-client.ts +9 -12
- package/src/client/attach-cause.ts +21 -0
- 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/nav-builder.ts +3 -1
- 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/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/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 +44 -7
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +7 -2
- 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/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/quasar/index.cjs
CHANGED
|
@@ -23,17 +23,160 @@ __export(quasar_exports, {
|
|
|
23
23
|
SA_PORTAL_CLASS: () => SA_PORTAL_CLASS,
|
|
24
24
|
bindSaThemeToDocument: () => bindSaThemeToDocument,
|
|
25
25
|
createSuperAdminApp: () => createSuperAdminApp,
|
|
26
|
+
quasarConfirm: () => quasarConfirm,
|
|
27
|
+
quasarConfirmOptions: () => quasarConfirmOptions,
|
|
26
28
|
quasarNotify: () => quasarNotify,
|
|
29
|
+
resolveQuasarOptions: () => resolveQuasarOptions,
|
|
30
|
+
useSuperAdminConfirm: () => useSuperAdminConfirm,
|
|
27
31
|
useSuperAdminNotify: () => useSuperAdminNotify
|
|
28
32
|
});
|
|
29
33
|
module.exports = __toCommonJS(quasar_exports);
|
|
30
34
|
|
|
31
35
|
// src/quasar/create-super-admin-app.ts
|
|
32
|
-
var
|
|
33
|
-
var
|
|
36
|
+
var import_vue7 = require("vue");
|
|
37
|
+
var import_quasar4 = require("quasar");
|
|
34
38
|
var import_pinia = require("pinia");
|
|
35
39
|
var import_vue_router = require("vue-router");
|
|
36
40
|
|
|
41
|
+
// src/client/attach-cause.ts
|
|
42
|
+
function attachCause(error, cause) {
|
|
43
|
+
Object.defineProperty(error, "cause", {
|
|
44
|
+
value: cause,
|
|
45
|
+
configurable: true,
|
|
46
|
+
writable: true
|
|
47
|
+
});
|
|
48
|
+
return error;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/client/i18n/format.ts
|
|
52
|
+
var import_types = require("@saasicat/types");
|
|
53
|
+
|
|
54
|
+
// src/client/admin-error.ts
|
|
55
|
+
var ADMIN_ERROR = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/AdminError");
|
|
56
|
+
var PLATFORM_ERROR = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/PlatformError");
|
|
57
|
+
function markPlatformError(error) {
|
|
58
|
+
Object.defineProperty(error, PLATFORM_ERROR, { value: true });
|
|
59
|
+
}
|
|
60
|
+
var TRANSPORT_FAILURE = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/TransportFailure");
|
|
61
|
+
function markTransportFailure(error) {
|
|
62
|
+
if (typeof error === "object" && error !== null) {
|
|
63
|
+
try {
|
|
64
|
+
Object.defineProperty(error, TRANSPORT_FAILURE, { value: true });
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return error;
|
|
69
|
+
}
|
|
70
|
+
function describe(init) {
|
|
71
|
+
const status = init.status ?? 0;
|
|
72
|
+
const where = [init.method, init.url].filter(Boolean).join(" ");
|
|
73
|
+
const what = status > 0 ? `HTTP ${status}` : "request failed";
|
|
74
|
+
const head = where ? `${where} \u2192 ${what}` : what;
|
|
75
|
+
const code = init.code ? ` (${init.code})` : "";
|
|
76
|
+
return init.detail ? `${head}${code}: ${init.detail}` : `${head}${code}`;
|
|
77
|
+
}
|
|
78
|
+
var AdminError = class extends Error {
|
|
79
|
+
/** HTTP status. `0` means the request never produced one. */
|
|
80
|
+
status;
|
|
81
|
+
/** Machine-readable code from the response body, when it carried one. */
|
|
82
|
+
code;
|
|
83
|
+
/** Parsed response body, when there was one. */
|
|
84
|
+
body;
|
|
85
|
+
/** Requested URL, when known. */
|
|
86
|
+
url;
|
|
87
|
+
/** Request method, when known. */
|
|
88
|
+
method;
|
|
89
|
+
/**
|
|
90
|
+
* Text the failing side supplied — the only candidate for user-facing
|
|
91
|
+
* output. `undefined` when nothing was supplied.
|
|
92
|
+
*/
|
|
93
|
+
detail;
|
|
94
|
+
/** The request completed but the answer was unusable. See `AdminErrorInit`. */
|
|
95
|
+
emptyResponse;
|
|
96
|
+
/** The request never reached the server. See `AdminErrorInit`. */
|
|
97
|
+
transportFailure;
|
|
98
|
+
constructor(init = {}) {
|
|
99
|
+
super(init.message ?? describe(init));
|
|
100
|
+
if (init.cause !== void 0) attachCause(this, init.cause);
|
|
101
|
+
this.name = "AdminError";
|
|
102
|
+
this.status = init.status ?? 0;
|
|
103
|
+
this.code = init.code;
|
|
104
|
+
this.body = init.body;
|
|
105
|
+
this.url = init.url;
|
|
106
|
+
this.method = init.method;
|
|
107
|
+
this.detail = init.detail;
|
|
108
|
+
this.emptyResponse = init.emptyResponse ?? false;
|
|
109
|
+
this.transportFailure = init.transportFailure ?? false;
|
|
110
|
+
Object.defineProperty(this, ADMIN_ERROR, { value: true });
|
|
111
|
+
markPlatformError(this);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
function asRecord(value) {
|
|
115
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
116
|
+
}
|
|
117
|
+
function asString(value) {
|
|
118
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
119
|
+
}
|
|
120
|
+
function readErrorDetail(body) {
|
|
121
|
+
const record = asRecord(body);
|
|
122
|
+
if (!record) return asString(body);
|
|
123
|
+
const message = record.message;
|
|
124
|
+
if (Array.isArray(message)) {
|
|
125
|
+
const parts = message.filter((part) => typeof part === "string");
|
|
126
|
+
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
127
|
+
}
|
|
128
|
+
return asString(message) ?? asString(record.error) ?? asString(record.reason);
|
|
129
|
+
}
|
|
130
|
+
function readErrorCode(body) {
|
|
131
|
+
return asString(asRecord(body)?.code);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/client/http-json.ts
|
|
135
|
+
function serverAnswered(status) {
|
|
136
|
+
return status > 0;
|
|
137
|
+
}
|
|
138
|
+
function requireServerAnswer(status, method, url, raise) {
|
|
139
|
+
if (serverAnswered(status)) return;
|
|
140
|
+
throw markTransportFailure(
|
|
141
|
+
raise(`${method} ${url} produced no HTTP status \u2014 the request did not reach the server`)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
function trimTrailingSlashes(url) {
|
|
145
|
+
let end = url.length;
|
|
146
|
+
while (end > 0 && url[end - 1] === "/") end--;
|
|
147
|
+
return url.slice(0, end);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/client/http/fetch-http-client.ts
|
|
151
|
+
var ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:|^\/\//i;
|
|
152
|
+
function resolveUrl(url, baseUrl) {
|
|
153
|
+
if (!baseUrl || ABSOLUTE_URL.test(url)) return url;
|
|
154
|
+
return `${trimTrailingSlashes(baseUrl)}${url.startsWith("/") ? "" : "/"}${url}`;
|
|
155
|
+
}
|
|
156
|
+
function createFetchHttpClient(options = {}) {
|
|
157
|
+
return async (url, init) => {
|
|
158
|
+
const headers = new Headers(await options.headers?.());
|
|
159
|
+
if (!headers.has("Accept")) headers.set("Accept", "application/json");
|
|
160
|
+
for (const [name, value] of Object.entries(init?.headers ?? {})) {
|
|
161
|
+
headers.set(name, value);
|
|
162
|
+
}
|
|
163
|
+
if (init?.body !== void 0 && !headers.has("Content-Type")) {
|
|
164
|
+
headers.set("Content-Type", "application/json");
|
|
165
|
+
}
|
|
166
|
+
let response;
|
|
167
|
+
try {
|
|
168
|
+
response = await fetch(resolveUrl(url, options.baseUrl), {
|
|
169
|
+
method: init?.method ?? "GET",
|
|
170
|
+
headers,
|
|
171
|
+
body: init?.body
|
|
172
|
+
});
|
|
173
|
+
} catch (err) {
|
|
174
|
+
throw markTransportFailure(err);
|
|
175
|
+
}
|
|
176
|
+
return response;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
37
180
|
// src/client/types.ts
|
|
38
181
|
var NOOP_KV_STORE = {
|
|
39
182
|
get: () => null,
|
|
@@ -75,7 +218,7 @@ function resolveLocalStorage() {
|
|
|
75
218
|
}
|
|
76
219
|
}
|
|
77
220
|
function defaultHttpClient() {
|
|
78
|
-
return (
|
|
221
|
+
return createFetchHttpClient();
|
|
79
222
|
}
|
|
80
223
|
|
|
81
224
|
// src/vue/super-admin-context.ts
|
|
@@ -148,15 +291,294 @@ var SUPER_ADMIN_NOTIFY_KEY = /* @__PURE__ */ Symbol.for(
|
|
|
148
291
|
"@saasicat/ui-vue/SUPER_ADMIN_NOTIFY"
|
|
149
292
|
);
|
|
150
293
|
|
|
151
|
-
// src/vue/
|
|
294
|
+
// src/vue/ui-confirm.ts
|
|
295
|
+
var SUPER_ADMIN_CONFIRM_KEY = /* @__PURE__ */ Symbol.for(
|
|
296
|
+
"@saasicat/ui-vue/SUPER_ADMIN_CONFIRM"
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// src/vue/resource-registry.ts
|
|
152
300
|
var import_vue = require("vue");
|
|
153
|
-
|
|
154
|
-
|
|
301
|
+
|
|
302
|
+
// src/client/resources/define-resource.ts
|
|
303
|
+
function defineResource(key, ops, options = {}) {
|
|
304
|
+
return { key, ops, projectScoped: options.projectScoped ?? false };
|
|
305
|
+
}
|
|
306
|
+
function requireProjectKey(def, ctx) {
|
|
307
|
+
if (!def.projectScoped) return;
|
|
308
|
+
if (typeof ctx.projectKey === "string" && ctx.projectKey.trim() !== "") return;
|
|
309
|
+
throw new Error(
|
|
310
|
+
`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().`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
function bindResource(def, http, ctx) {
|
|
314
|
+
const readContext = typeof ctx === "function" ? ctx : () => ctx;
|
|
315
|
+
if (def.projectScoped) requireProjectKey(def, readContext());
|
|
316
|
+
const bound = {};
|
|
317
|
+
for (const [name, op] of Object.entries(def.ops)) {
|
|
318
|
+
bound[name] = async (...args) => {
|
|
319
|
+
const context = readContext();
|
|
320
|
+
requireProjectKey(def, context);
|
|
321
|
+
return op(http, context, ...args);
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return bound;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/client/resources/resource-request.ts
|
|
328
|
+
async function requestJson(http, url, init = {}) {
|
|
329
|
+
const method = init.method ?? "GET";
|
|
330
|
+
const response = await http(url, {
|
|
331
|
+
method,
|
|
332
|
+
headers: { "content-type": "application/json", ...init.headers },
|
|
333
|
+
body: init.body === void 0 ? void 0 : JSON.stringify(init.body)
|
|
334
|
+
});
|
|
335
|
+
requireServerAnswer(
|
|
336
|
+
response.status,
|
|
337
|
+
method,
|
|
338
|
+
url,
|
|
339
|
+
(diagnostic) => new AdminError({ status: response.status, url, method, message: diagnostic })
|
|
340
|
+
);
|
|
341
|
+
if (response.status === 204) return null;
|
|
342
|
+
const body = await response.json().catch(() => null);
|
|
343
|
+
if (response.status >= 400) {
|
|
344
|
+
throw new AdminError({
|
|
345
|
+
status: response.status,
|
|
346
|
+
code: readErrorCode(body),
|
|
347
|
+
body,
|
|
348
|
+
// The third place this had to be read, and the second time it was
|
|
349
|
+
// read a narrower way: without `detail` a page migrating onto these
|
|
350
|
+
// resources loses "Plan already exists" and shows the generic
|
|
351
|
+
// status sentence instead. One decision, one function.
|
|
352
|
+
detail: readErrorDetail(body),
|
|
353
|
+
url,
|
|
354
|
+
method
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
return body;
|
|
358
|
+
}
|
|
359
|
+
async function requestJsonBody(http, url, what, init = {}) {
|
|
360
|
+
const body = await requestJson(http, url, init);
|
|
361
|
+
if (body === null || body === void 0) {
|
|
362
|
+
throw new AdminError({
|
|
363
|
+
status: 0,
|
|
364
|
+
url,
|
|
365
|
+
method: init.method ?? "GET",
|
|
366
|
+
// `what` is a diagnostic — "Create returned no body" — so it goes
|
|
367
|
+
// on `message`, not `detail`. As `detail` it out-ranked the
|
|
368
|
+
// localized wording and the operator read an English internal
|
|
369
|
+
// string instead of being told the change may already have landed.
|
|
370
|
+
message: what,
|
|
371
|
+
emptyResponse: true
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return body;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/client/resources/list-resource.ts
|
|
378
|
+
var LIST_FIRST_PAGE = 1;
|
|
379
|
+
var LIST_PAGE_SIZE_DEFAULT = 50;
|
|
380
|
+
function isSentInQuery(value) {
|
|
381
|
+
return value !== void 0 && value !== null && value !== "";
|
|
382
|
+
}
|
|
383
|
+
function appendFilter(params, filter) {
|
|
384
|
+
for (const [key, value] of Object.entries(filter ?? {})) {
|
|
385
|
+
if (!isSentInQuery(value)) continue;
|
|
386
|
+
params.set(key, String(value));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function listUrl(endpoint, query = {}) {
|
|
390
|
+
const params = new URLSearchParams();
|
|
391
|
+
params.set("page", String(query.page ?? LIST_FIRST_PAGE));
|
|
392
|
+
params.set("pageSize", String(query.pageSize ?? LIST_PAGE_SIZE_DEFAULT));
|
|
393
|
+
appendFilter(params, query.filter);
|
|
394
|
+
return `${endpoint}${endpoint.includes("?") ? "&" : "?"}${params.toString()}`;
|
|
395
|
+
}
|
|
396
|
+
function readListPage(raw) {
|
|
397
|
+
if (Array.isArray(raw)) return { items: raw, total: raw.length };
|
|
398
|
+
if (raw === null || typeof raw !== "object") return { items: [], total: 0 };
|
|
399
|
+
const body = raw;
|
|
400
|
+
const page = {
|
|
401
|
+
items: Array.isArray(body.items) ? body.items : []
|
|
402
|
+
};
|
|
403
|
+
if (typeof body.total === "number") page.total = body.total;
|
|
404
|
+
if (typeof body.page === "number") page.page = body.page;
|
|
405
|
+
if (typeof body.pageSize === "number") page.pageSize = body.pageSize;
|
|
406
|
+
return page;
|
|
407
|
+
}
|
|
408
|
+
function defineListOp(endpoint) {
|
|
409
|
+
return async (http, ctx, query) => readListPage(await requestJson(http, listUrl(endpoint(ctx), query)));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/client/resources/plans.resource.ts
|
|
413
|
+
function plansUrl(ctx) {
|
|
414
|
+
return `${ctx.apiBase}/catalog/plans`;
|
|
415
|
+
}
|
|
416
|
+
function versionsUrl(ctx) {
|
|
417
|
+
return `${ctx.apiBase}/catalog/plan-versions`;
|
|
418
|
+
}
|
|
419
|
+
function scoped(url, ctx) {
|
|
420
|
+
return `${url}?projectKey=${encodeURIComponent(ctx.projectKey)}`;
|
|
421
|
+
}
|
|
422
|
+
var plansResource = defineResource(
|
|
423
|
+
"plans",
|
|
424
|
+
{
|
|
425
|
+
list: async (http, ctx) => await requestJson(http, scoped(plansUrl(ctx), ctx)) ?? [],
|
|
426
|
+
tenantCounts: async (http, ctx) => await requestJson(
|
|
427
|
+
http,
|
|
428
|
+
scoped(`${plansUrl(ctx)}/tenant-counts`, ctx)
|
|
429
|
+
) ?? {},
|
|
430
|
+
/**
|
|
431
|
+
* The project comes from the context, not from the caller.
|
|
432
|
+
*
|
|
433
|
+
* The endpoint takes it in the body, and the composable this replaces
|
|
434
|
+
* made that the caller's job — so a page had to keep hold of the
|
|
435
|
+
* project it was administering just to create in it, and `list()` and
|
|
436
|
+
* `create()` read two different sources for one value. A stale one
|
|
437
|
+
* creates a plan outside the project the list is showing.
|
|
438
|
+
*/
|
|
439
|
+
create: (http, ctx, data) => requestJsonBody(http, plansUrl(ctx), "Create returned no body", {
|
|
440
|
+
method: "POST",
|
|
441
|
+
body: { ...data, projectKey: ctx.projectKey }
|
|
442
|
+
}),
|
|
443
|
+
update: (http, ctx, planId, data) => requestJsonBody(
|
|
444
|
+
http,
|
|
445
|
+
`${plansUrl(ctx)}/${planId}`,
|
|
446
|
+
"Update returned no body",
|
|
447
|
+
{
|
|
448
|
+
method: "PATCH",
|
|
449
|
+
body: data
|
|
450
|
+
}
|
|
451
|
+
),
|
|
452
|
+
/** Marks the plan deleted. Fails while it still has versions. */
|
|
453
|
+
softDelete: async (http, ctx, planId) => {
|
|
454
|
+
await requestJson(http, `${plansUrl(ctx)}/${planId}`, { method: "DELETE" });
|
|
455
|
+
},
|
|
456
|
+
/** Removes the plan for good. 422 `PLAN_HAS_VERSIONS` when it cannot. */
|
|
457
|
+
hardDelete: async (http, ctx, planId) => {
|
|
458
|
+
await requestJson(http, `${plansUrl(ctx)}/${planId}/purge`, { method: "DELETE" });
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
{ projectScoped: true }
|
|
462
|
+
);
|
|
463
|
+
var planVersionsResource = defineResource("planVersions", {
|
|
464
|
+
listForPlan: async (http, ctx, planId) => await requestJson(http, `${plansUrl(ctx)}/${planId}/versions`) ?? [],
|
|
465
|
+
createDraft: (http, ctx, planId, data) => requestJsonBody(
|
|
466
|
+
http,
|
|
467
|
+
`${plansUrl(ctx)}/${planId}/versions`,
|
|
468
|
+
"CreateDraft returned no body",
|
|
469
|
+
{ method: "POST", body: data }
|
|
470
|
+
),
|
|
471
|
+
updateDraft: (http, ctx, versionId, data) => requestJsonBody(
|
|
472
|
+
http,
|
|
473
|
+
`${versionsUrl(ctx)}/${versionId}`,
|
|
474
|
+
"UpdateDraft returned no body",
|
|
475
|
+
{ method: "PATCH", body: data }
|
|
476
|
+
),
|
|
477
|
+
publish: (http, ctx, versionId, options = {}) => requestJsonBody(
|
|
478
|
+
http,
|
|
479
|
+
`${versionsUrl(ctx)}/${versionId}/publish`,
|
|
480
|
+
"Publish returned no body",
|
|
481
|
+
{ method: "POST", body: options }
|
|
482
|
+
),
|
|
483
|
+
discardDraft: async (http, ctx, versionId) => {
|
|
484
|
+
await requestJson(http, `${versionsUrl(ctx)}/${versionId}`, { method: "DELETE" });
|
|
485
|
+
},
|
|
486
|
+
/**
|
|
487
|
+
* Ends a live version on `endsAt`, with no successor. Idempotent — a
|
|
488
|
+
* second call with another date overwrites the first.
|
|
489
|
+
*
|
|
490
|
+
* `endsAt` is the date itself, not a payload: the endpoint's body is
|
|
491
|
+
* `{ endsAt }` and wrapping it is this operation's job, not the caller's.
|
|
492
|
+
*/
|
|
493
|
+
terminate: (http, ctx, versionId, endsAt) => requestJsonBody(
|
|
494
|
+
http,
|
|
495
|
+
`${versionsUrl(ctx)}/${versionId}/terminate`,
|
|
496
|
+
"Terminate returned no body",
|
|
497
|
+
{ method: "POST", body: { endsAt } }
|
|
498
|
+
)
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// src/client/resources/tenants.resource.ts
|
|
502
|
+
var tenantsResource = defineResource("tenants", {
|
|
503
|
+
list: defineListOp((ctx) => `${ctx.apiBase}/tenants`)
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// src/client/resources/audit.resource.ts
|
|
507
|
+
var auditResource = defineResource("audit", {
|
|
508
|
+
list: defineListOp((ctx) => `${ctx.apiBase}/audit`)
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// src/client/resources/index.ts
|
|
512
|
+
var platformResources = {
|
|
513
|
+
plans: plansResource,
|
|
514
|
+
planVersions: planVersionsResource,
|
|
515
|
+
tenants: tenantsResource,
|
|
516
|
+
audit: auditResource
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
// src/vue/resource-registry.ts
|
|
520
|
+
function boundWithOverride(def, http, readContext, override) {
|
|
521
|
+
const context = override?.context ? () => ({ ...readContext(), ...override.context }) : readContext;
|
|
522
|
+
const platform = bindResource(def, override?.http ?? http, context);
|
|
523
|
+
if (!override?.ops) return platform;
|
|
524
|
+
const result = { ...platform };
|
|
525
|
+
for (const [name, wrap] of Object.entries(override.ops)) {
|
|
526
|
+
if (typeof wrap !== "function") continue;
|
|
527
|
+
if (!Object.hasOwn(platform, name)) {
|
|
528
|
+
throw new Error(
|
|
529
|
+
`createResourceRegistry: resource "${def.key}" has no operation "${name}" to override. It offers: ${Object.keys(platform).join(", ")}.`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
const next = platform[name];
|
|
533
|
+
result[name] = (...args) => wrap(next, ...args);
|
|
534
|
+
}
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
function createResourceRegistry(options) {
|
|
538
|
+
if (!options?.http) {
|
|
539
|
+
throw new Error(
|
|
540
|
+
"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."
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
const readContext = typeof options.context === "function" ? options.context : () => options.context;
|
|
544
|
+
const unknown = Object.keys(options.overrides ?? {}).filter(
|
|
545
|
+
(key) => !Object.hasOwn(options.resources, key)
|
|
546
|
+
);
|
|
547
|
+
if (unknown.length > 0) {
|
|
548
|
+
throw new Error(
|
|
549
|
+
`createResourceRegistry: no resource named ${unknown.map((k) => `"${k}"`).join(", ")} to override. The registry holds: ${Object.keys(options.resources).join(", ")}.`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
const bound = /* @__PURE__ */ new Map();
|
|
553
|
+
for (const [key, def] of Object.entries(options.resources)) {
|
|
554
|
+
bound.set(key, boundWithOverride(def, options.http, readContext, options.overrides?.[key]));
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
get(key) {
|
|
558
|
+
const ops = bound.get(key);
|
|
559
|
+
if (!ops) {
|
|
560
|
+
throw new Error(
|
|
561
|
+
`useResource("${String(key)}"): no such resource. The registry offers: ${[...bound.keys()].join(", ")}.`
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
return ops;
|
|
565
|
+
},
|
|
566
|
+
keys: () => [...bound.keys()]
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
var SUPER_ADMIN_RESOURCES_KEY = /* @__PURE__ */ Symbol.for(
|
|
570
|
+
"@saasicat/ui-vue/SUPER_ADMIN_RESOURCES"
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
// src/vue/use-sa-theme.ts
|
|
574
|
+
var import_vue2 = require("vue");
|
|
575
|
+
var SA_COLOR_SCHEMES = ["light", "dark", "system"];
|
|
576
|
+
var SA_THEME_STORAGE_KEY = "saasicat.theme.scheme";
|
|
155
577
|
function isScheme(value) {
|
|
156
|
-
return typeof value === "string" &&
|
|
578
|
+
return typeof value === "string" && SA_COLOR_SCHEMES.includes(value);
|
|
157
579
|
}
|
|
158
580
|
function systemScheme() {
|
|
159
|
-
const value = (0,
|
|
581
|
+
const value = (0, import_vue2.ref)("light");
|
|
160
582
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
161
583
|
return { value, unsubscribe: () => {
|
|
162
584
|
} };
|
|
@@ -173,14 +595,20 @@ function createSaTheme(options = {}) {
|
|
|
173
595
|
const storage = options.storage ?? defaultKvStore();
|
|
174
596
|
const persist = options.persist !== false;
|
|
175
597
|
const provided = options.scheme;
|
|
176
|
-
const appOwnsIt = (0,
|
|
177
|
-
const
|
|
178
|
-
const
|
|
598
|
+
const appOwnsIt = (0, import_vue2.isRef)(provided);
|
|
599
|
+
const key = `${options.storageKeyPrefix ?? ""}${SA_THEME_STORAGE_KEY}`;
|
|
600
|
+
const stored = persist && !appOwnsIt ? storage.get(key) : null;
|
|
601
|
+
const scheme = (0, import_vue2.isRef)(provided) ? provided : (0, import_vue2.ref)(isScheme(stored) ? stored : provided ?? "system");
|
|
179
602
|
const { value: system, unsubscribe } = systemScheme();
|
|
180
|
-
const stopPersisting = persist && !appOwnsIt ? (0,
|
|
603
|
+
const stopPersisting = persist && !appOwnsIt ? (0, import_vue2.watch)(scheme, (next) => storage.set(key, next)) : null;
|
|
181
604
|
return {
|
|
182
605
|
scheme,
|
|
183
|
-
resolved: (0,
|
|
606
|
+
resolved: (0, import_vue2.computed)(() => scheme.value === "system" ? system.value : scheme.value),
|
|
607
|
+
// A readonly ref (typically a `computed` derived from a profile
|
|
608
|
+
// setting) silently swallows writes, so the switcher would be a dead
|
|
609
|
+
// control. TypeScript ignores `readonly` when checking assignability,
|
|
610
|
+
// so the guard has to be a runtime one — same as the locale next door.
|
|
611
|
+
switcherEnabled: (options.switcher ?? true) && !(0, import_vue2.isReadonly)(scheme),
|
|
184
612
|
dispose: () => {
|
|
185
613
|
unsubscribe();
|
|
186
614
|
stopPersisting?.();
|
|
@@ -190,7 +618,7 @@ function createSaTheme(options = {}) {
|
|
|
190
618
|
var SA_THEME_KEY = /* @__PURE__ */ Symbol.for("@saasicat/ui-vue/SA_THEME");
|
|
191
619
|
|
|
192
620
|
// src/vue/use-super-admin-i18n.ts
|
|
193
|
-
var
|
|
621
|
+
var import_vue3 = require("vue");
|
|
194
622
|
|
|
195
623
|
// src/client/i18n/locale.ts
|
|
196
624
|
var SA_LOCALES = ["de", "en"];
|
|
@@ -228,9 +656,6 @@ function mergeMessages(base, overrides) {
|
|
|
228
656
|
return result;
|
|
229
657
|
}
|
|
230
658
|
|
|
231
|
-
// src/client/i18n/format.ts
|
|
232
|
-
var import_types2 = require("@saasicat/types");
|
|
233
|
-
|
|
234
659
|
// src/client/i18n/messages/audit.ts
|
|
235
660
|
var auditMessages = defineMessages(
|
|
236
661
|
{
|
|
@@ -905,9 +1330,6 @@ var discoveryMessages = defineMessages(
|
|
|
905
1330
|
replacedBy: "ersetzt durch {key}",
|
|
906
1331
|
replaces: "ersetzt: {keys}",
|
|
907
1332
|
reapproveEmphasis: "erneut freigeben",
|
|
908
|
-
errorDiscoveryHttp: "Discovery-Endpoint antwortete mit HTTP {status}",
|
|
909
|
-
errorRescanHttp: "Discovery-Rescan antwortete mit HTTP {status}",
|
|
910
|
-
errorCatalogHttp: "Catalog-Entries-API antwortete mit HTTP {status}",
|
|
911
1333
|
capList: {
|
|
912
1334
|
orphanFeature: "Feature im Katalog ohne implementierende Capability \u2014 blockiert im blocking-Strict-Mode das Plan-Publish.",
|
|
913
1335
|
removedFromCode: "aus Code entfernt"
|
|
@@ -1009,9 +1431,6 @@ var discoveryMessages = defineMessages(
|
|
|
1009
1431
|
replacedBy: "replaced by {key}",
|
|
1010
1432
|
replaces: "replaces: {keys}",
|
|
1011
1433
|
reapproveEmphasis: "approve again",
|
|
1012
|
-
errorDiscoveryHttp: "Discovery endpoint responded with HTTP {status}",
|
|
1013
|
-
errorRescanHttp: "Discovery rescan responded with HTTP {status}",
|
|
1014
|
-
errorCatalogHttp: "Catalog entries API responded with HTTP {status}",
|
|
1015
1434
|
capList: {
|
|
1016
1435
|
orphanFeature: "Feature in the catalog without an implementing capability \u2014 blocks plan publishing in blocking strict mode.",
|
|
1017
1436
|
removedFromCode: "removed from code"
|
|
@@ -1216,6 +1635,36 @@ var emailMessages = defineMessages(
|
|
|
1216
1635
|
}
|
|
1217
1636
|
);
|
|
1218
1637
|
|
|
1638
|
+
// src/client/i18n/messages/errors.ts
|
|
1639
|
+
var errorsMessages = defineMessages(
|
|
1640
|
+
{
|
|
1641
|
+
network: "Die Anfrage konnte nicht abgeschlossen werden. Bitte Verbindung pr\xFCfen.",
|
|
1642
|
+
emptyResponse: "Der Server hat nichts zur\xFCckgegeben. Bitte pr\xFCfen, ob die \xC4nderung angekommen ist.",
|
|
1643
|
+
unauthorized: "Die Sitzung ist abgelaufen. Bitte erneut anmelden.",
|
|
1644
|
+
forbidden: "F\xFCr diese Aktion fehlt die Berechtigung.",
|
|
1645
|
+
notFound: "Der angeforderte Eintrag existiert nicht mehr.",
|
|
1646
|
+
conflict: "Der Eintrag wurde zwischenzeitlich ge\xE4ndert. Bitte neu laden.",
|
|
1647
|
+
validation: "Die Eingaben wurden nicht akzeptiert.",
|
|
1648
|
+
rateLimited: "Zu viele Anfragen. Bitte kurz warten und erneut versuchen.",
|
|
1649
|
+
server: "Der Server konnte die Anfrage nicht verarbeiten.",
|
|
1650
|
+
httpStatus: "Die Anfrage ist fehlgeschlagen (HTTP {status}).",
|
|
1651
|
+
unexpected: "Es ist ein unerwarteter Fehler aufgetreten. Bitte erneut versuchen."
|
|
1652
|
+
},
|
|
1653
|
+
{
|
|
1654
|
+
network: "The request could not be completed. Please check your connection.",
|
|
1655
|
+
emptyResponse: "The server returned nothing. Please check whether the change was applied.",
|
|
1656
|
+
unauthorized: "Your session has expired. Please sign in again.",
|
|
1657
|
+
forbidden: "You do not have permission for this action.",
|
|
1658
|
+
notFound: "The requested entry no longer exists.",
|
|
1659
|
+
conflict: "The entry changed in the meantime. Please reload.",
|
|
1660
|
+
validation: "The input was not accepted.",
|
|
1661
|
+
rateLimited: "Too many requests. Please wait a moment and try again.",
|
|
1662
|
+
server: "The server could not process the request.",
|
|
1663
|
+
httpStatus: "The request failed (HTTP {status}).",
|
|
1664
|
+
unexpected: "Something went wrong. Please try again."
|
|
1665
|
+
}
|
|
1666
|
+
);
|
|
1667
|
+
|
|
1219
1668
|
// src/client/i18n/messages/marketing.ts
|
|
1220
1669
|
var marketingMessages = defineMessages(
|
|
1221
1670
|
{
|
|
@@ -1340,9 +1789,6 @@ var marketingMessages = defineMessages(
|
|
|
1340
1789
|
colorLabel: "Farbe",
|
|
1341
1790
|
delete: "Aktion l\xF6schen",
|
|
1342
1791
|
deleteConfirm: "Aktion \u201E{label}\u201C wirklich l\xF6schen?"
|
|
1343
|
-
},
|
|
1344
|
-
errors: {
|
|
1345
|
-
projectionsApi: "MarketingProjections-API antwortete mit HTTP {status}"
|
|
1346
1792
|
}
|
|
1347
1793
|
},
|
|
1348
1794
|
{
|
|
@@ -1467,9 +1913,6 @@ var marketingMessages = defineMessages(
|
|
|
1467
1913
|
colorLabel: "Color",
|
|
1468
1914
|
delete: "Delete promotion",
|
|
1469
1915
|
deleteConfirm: 'Really delete promotion "{label}"?'
|
|
1470
|
-
},
|
|
1471
|
-
errors: {
|
|
1472
|
-
projectionsApi: "MarketingProjections API responded with HTTP {status}"
|
|
1473
1916
|
}
|
|
1474
1917
|
}
|
|
1475
1918
|
);
|
|
@@ -2713,7 +3156,6 @@ var promosMessages = defineMessages(
|
|
|
2713
3156
|
{
|
|
2714
3157
|
title: "Promo-Codes",
|
|
2715
3158
|
createAction: "Promo-Code anlegen",
|
|
2716
|
-
apiErrorHttpStatus: "Promotions-API antwortete mit HTTP {status}",
|
|
2717
3159
|
list: {
|
|
2718
3160
|
searchLabel: "Suche (Code)",
|
|
2719
3161
|
tileScheduled: "Geplant",
|
|
@@ -2811,7 +3253,6 @@ var promosMessages = defineMessages(
|
|
|
2811
3253
|
{
|
|
2812
3254
|
title: "Promo codes",
|
|
2813
3255
|
createAction: "Create promo code",
|
|
2814
|
-
apiErrorHttpStatus: "Promotions API responded with HTTP {status}",
|
|
2815
3256
|
list: {
|
|
2816
3257
|
searchLabel: "Search (code)",
|
|
2817
3258
|
tileScheduled: "Scheduled",
|
|
@@ -2919,7 +3360,13 @@ var shellMessages = defineMessages(
|
|
|
2919
3360
|
subtitle: "SuperAdmin \xB7 Plattform-Verwaltung",
|
|
2920
3361
|
roleBadge: "SUPER ADMIN",
|
|
2921
3362
|
logout: "Abmelden",
|
|
2922
|
-
language: "Sprache"
|
|
3363
|
+
language: "Sprache",
|
|
3364
|
+
theme: {
|
|
3365
|
+
label: "Darstellung",
|
|
3366
|
+
light: "Hell",
|
|
3367
|
+
dark: "Dunkel",
|
|
3368
|
+
system: "System"
|
|
3369
|
+
}
|
|
2923
3370
|
},
|
|
2924
3371
|
drawer: {
|
|
2925
3372
|
docs: "Doku \xF6ffnen"
|
|
@@ -2988,7 +3435,13 @@ var shellMessages = defineMessages(
|
|
|
2988
3435
|
subtitle: "SuperAdmin \xB7 Platform administration",
|
|
2989
3436
|
roleBadge: "SUPER ADMIN",
|
|
2990
3437
|
logout: "Sign out",
|
|
2991
|
-
language: "Language"
|
|
3438
|
+
language: "Language",
|
|
3439
|
+
theme: {
|
|
3440
|
+
label: "Appearance",
|
|
3441
|
+
light: "Light",
|
|
3442
|
+
dark: "Dark",
|
|
3443
|
+
system: "System"
|
|
3444
|
+
}
|
|
2992
3445
|
},
|
|
2993
3446
|
drawer: {
|
|
2994
3447
|
docs: "Open documentation"
|
|
@@ -3232,6 +3685,7 @@ var usersMessages = defineMessages(
|
|
|
3232
3685
|
function catalogFor(locale) {
|
|
3233
3686
|
return {
|
|
3234
3687
|
common: commonMessages[locale],
|
|
3688
|
+
errors: errorsMessages[locale],
|
|
3235
3689
|
nav: navMessages[locale],
|
|
3236
3690
|
shell: shellMessages[locale],
|
|
3237
3691
|
dashboard: dashboardMessages[locale],
|
|
@@ -3293,13 +3747,13 @@ var SUPER_ADMIN_I18N_KEY = /* @__PURE__ */ Symbol.for(
|
|
|
3293
3747
|
);
|
|
3294
3748
|
function createSuperAdminI18n(options = {}) {
|
|
3295
3749
|
const catalog = createSaCatalog(options);
|
|
3296
|
-
const locale = (0,
|
|
3297
|
-
const active = (0,
|
|
3750
|
+
const locale = (0, import_vue3.isRef)(options.locale) ? options.locale : createOwnedLocale(options, catalog);
|
|
3751
|
+
const active = (0, import_vue3.computed)(
|
|
3298
3752
|
() => catalog.has(locale.value) ? locale.value : catalog.defaultLocale
|
|
3299
3753
|
);
|
|
3300
|
-
const messages = (0,
|
|
3301
|
-
const intlLocale = (0,
|
|
3302
|
-
const writable = !(0,
|
|
3754
|
+
const messages = (0, import_vue3.computed)(() => catalog.messagesFor(active.value));
|
|
3755
|
+
const intlLocale = (0, import_vue3.computed)(() => catalog.intlLocaleFor(active.value));
|
|
3756
|
+
const writable = !(0, import_vue3.isReadonly)(locale);
|
|
3303
3757
|
return {
|
|
3304
3758
|
locale,
|
|
3305
3759
|
messages,
|
|
@@ -3310,35 +3764,43 @@ function createSuperAdminI18n(options = {}) {
|
|
|
3310
3764
|
}
|
|
3311
3765
|
function createOwnedLocale(options, catalog) {
|
|
3312
3766
|
const initial = options.locale ?? catalog.defaultLocale;
|
|
3313
|
-
if (options.persist === false) return (0,
|
|
3767
|
+
if (options.persist === false) return (0, import_vue3.ref)(initial);
|
|
3314
3768
|
const storage = options.storage ?? defaultKvStore();
|
|
3315
3769
|
const key = `${options.storageKeyPrefix ?? ""}${SA_LOCALE_STORAGE_KEY}`;
|
|
3316
3770
|
const stored = storage.get(key);
|
|
3317
|
-
const locale = (0,
|
|
3318
|
-
(0,
|
|
3771
|
+
const locale = (0, import_vue3.ref)(stored && catalog.has(stored) ? stored : initial);
|
|
3772
|
+
(0, import_vue3.watch)(locale, (next) => storage.set(key, next));
|
|
3319
3773
|
return locale;
|
|
3320
3774
|
}
|
|
3321
3775
|
|
|
3322
3776
|
// src/quasar/dark-bridge.ts
|
|
3323
3777
|
var import_quasar = require("quasar");
|
|
3324
|
-
var
|
|
3778
|
+
var import_vue4 = require("vue");
|
|
3325
3779
|
function bindSaThemeToDocument(theme) {
|
|
3326
|
-
|
|
3780
|
+
let mirroring = false;
|
|
3781
|
+
const toQuasar = (0, import_vue4.watch)(
|
|
3327
3782
|
theme.resolved,
|
|
3328
3783
|
(resolved) => {
|
|
3329
3784
|
if (typeof document !== "undefined") {
|
|
3330
3785
|
document.documentElement.setAttribute("data-sa-theme", resolved);
|
|
3331
3786
|
}
|
|
3332
|
-
|
|
3787
|
+
mirroring = true;
|
|
3788
|
+
try {
|
|
3789
|
+
import_quasar.Dark.set(resolved === "dark");
|
|
3790
|
+
} finally {
|
|
3791
|
+
mirroring = false;
|
|
3792
|
+
}
|
|
3333
3793
|
},
|
|
3334
3794
|
{ immediate: true }
|
|
3335
3795
|
);
|
|
3336
|
-
const fromQuasar = (0,
|
|
3337
|
-
() => import_quasar.Dark.
|
|
3338
|
-
(
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3796
|
+
const fromQuasar = (0, import_vue4.watch)(
|
|
3797
|
+
() => import_quasar.Dark.mode,
|
|
3798
|
+
(mode) => {
|
|
3799
|
+
if (mirroring) return;
|
|
3800
|
+
const picked = mode === "auto" ? "system" : mode === true ? "dark" : "light";
|
|
3801
|
+
if (theme.scheme.value !== picked) theme.scheme.value = picked;
|
|
3802
|
+
},
|
|
3803
|
+
{ flush: "sync" }
|
|
3342
3804
|
);
|
|
3343
3805
|
return () => {
|
|
3344
3806
|
toQuasar();
|
|
@@ -3346,8 +3808,18 @@ function bindSaThemeToDocument(theme) {
|
|
|
3346
3808
|
};
|
|
3347
3809
|
}
|
|
3348
3810
|
|
|
3811
|
+
// src/vue/platform-loaders.ts
|
|
3812
|
+
function resolveSuperAdminEndpoints(endpoints) {
|
|
3813
|
+
return {
|
|
3814
|
+
apiBase: endpoints.apiBase,
|
|
3815
|
+
publicBootEndpoint: endpoints.publicBootEndpoint ?? `${endpoints.apiBase}/boot`,
|
|
3816
|
+
manifestEndpoint: endpoints.manifestEndpoint ?? `${endpoints.apiBase}/manifest`,
|
|
3817
|
+
projectKey: endpoints.projectKey ?? ""
|
|
3818
|
+
};
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3349
3821
|
// src/quasar/notify.ts
|
|
3350
|
-
var
|
|
3822
|
+
var import_vue5 = require("vue");
|
|
3351
3823
|
var import_quasar2 = require("quasar");
|
|
3352
3824
|
var quasarNotify = (kind, message, options) => {
|
|
3353
3825
|
import_quasar2.Notify.create({
|
|
@@ -3359,21 +3831,54 @@ var quasarNotify = (kind, message, options) => {
|
|
|
3359
3831
|
});
|
|
3360
3832
|
};
|
|
3361
3833
|
function useSuperAdminNotify() {
|
|
3362
|
-
return (0,
|
|
3834
|
+
return (0, import_vue5.inject)(SUPER_ADMIN_NOTIFY_KEY, quasarNotify);
|
|
3835
|
+
}
|
|
3836
|
+
|
|
3837
|
+
// src/quasar/confirm.ts
|
|
3838
|
+
var import_vue6 = require("vue");
|
|
3839
|
+
var import_quasar3 = require("quasar");
|
|
3840
|
+
function quasarConfirmOptions(request) {
|
|
3841
|
+
return {
|
|
3842
|
+
title: request.title,
|
|
3843
|
+
message: request.message,
|
|
3844
|
+
cancel: request.cancelLabel,
|
|
3845
|
+
ok: { label: request.confirmLabel, color: request.tone ?? "primary" },
|
|
3846
|
+
...request.prompt ? {
|
|
3847
|
+
prompt: {
|
|
3848
|
+
model: request.prompt.initial ?? "",
|
|
3849
|
+
type: request.prompt.type ?? "text"
|
|
3850
|
+
}
|
|
3851
|
+
} : {}
|
|
3852
|
+
};
|
|
3853
|
+
}
|
|
3854
|
+
var quasarConfirm = (request) => new Promise((resolve) => {
|
|
3855
|
+
import_quasar3.Dialog.create(quasarConfirmOptions(request)).onOk((value) => {
|
|
3856
|
+
resolve({ ok: true, value: typeof value === "string" ? value : void 0 });
|
|
3857
|
+
}).onCancel(() => resolve({ ok: false }));
|
|
3858
|
+
});
|
|
3859
|
+
function useSuperAdminConfirm() {
|
|
3860
|
+
return (0, import_vue6.inject)(SUPER_ADMIN_CONFIRM_KEY, quasarConfirm);
|
|
3363
3861
|
}
|
|
3364
3862
|
|
|
3365
3863
|
// src/quasar/create-super-admin-app.ts
|
|
3366
3864
|
var SA_PORTAL_CLASS = "sa-portal";
|
|
3367
3865
|
var DEFAULT_QUASAR_OPTIONS = {
|
|
3368
|
-
plugins: { Notify:
|
|
3866
|
+
plugins: { Notify: import_quasar4.Notify, Dialog: import_quasar4.Dialog, Loading: import_quasar4.Loading },
|
|
3369
3867
|
config: { notify: { position: "top-right", timeout: 3e3 } }
|
|
3370
3868
|
};
|
|
3869
|
+
function resolveQuasarOptions(own) {
|
|
3870
|
+
if (!own) return DEFAULT_QUASAR_OPTIONS;
|
|
3871
|
+
return {
|
|
3872
|
+
...own,
|
|
3873
|
+
plugins: { ...DEFAULT_QUASAR_OPTIONS.plugins, ...own.plugins }
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3371
3876
|
function createSuperAdminApp(options) {
|
|
3372
|
-
const app = (0,
|
|
3373
|
-
const quasarOptions = options.quasarOptions
|
|
3877
|
+
const app = (0, import_vue7.createApp)(options.rootComponent);
|
|
3878
|
+
const quasarOptions = resolveQuasarOptions(options.quasarOptions);
|
|
3374
3879
|
const config = quasarOptions.config;
|
|
3375
3880
|
const existingPortalClass = config?.globalNodes?.class;
|
|
3376
|
-
app.use(
|
|
3881
|
+
app.use(import_quasar4.Quasar, {
|
|
3377
3882
|
...quasarOptions,
|
|
3378
3883
|
config: {
|
|
3379
3884
|
...quasarOptions.config,
|
|
@@ -3393,11 +3898,8 @@ function createSuperAdminApp(options) {
|
|
|
3393
3898
|
router.beforeEach(navGuard);
|
|
3394
3899
|
}
|
|
3395
3900
|
app.use(router);
|
|
3396
|
-
const endpoints =
|
|
3397
|
-
|
|
3398
|
-
publicBootEndpoint: options.endpoints.publicBootEndpoint ?? `${options.endpoints.apiBase}/boot`,
|
|
3399
|
-
manifestEndpoint: options.endpoints.manifestEndpoint ?? `${options.endpoints.apiBase}/manifest`
|
|
3400
|
-
};
|
|
3901
|
+
const endpoints = resolveSuperAdminEndpoints(options.endpoints);
|
|
3902
|
+
const http = options.http ?? defaultHttpClient();
|
|
3401
3903
|
const i18n = createSuperAdminI18n(options.i18n);
|
|
3402
3904
|
const configuredDark = options.quasarOptions?.config?.dark;
|
|
3403
3905
|
const theme = createSaTheme({
|
|
@@ -3412,6 +3914,23 @@ function createSuperAdminApp(options) {
|
|
|
3412
3914
|
app.provide(SUPER_ADMIN_EXTENSIONS_KEY, options.extensions ?? {});
|
|
3413
3915
|
app.provide(SUPER_ADMIN_ACTIONS_KEY, options.actions ?? {});
|
|
3414
3916
|
app.provide(SUPER_ADMIN_NOTIFY_KEY, options.notify ?? quasarNotify);
|
|
3917
|
+
app.provide(SUPER_ADMIN_CONFIRM_KEY, options.confirm ?? quasarConfirm);
|
|
3918
|
+
if (options.http) {
|
|
3919
|
+
app.provide(
|
|
3920
|
+
SUPER_ADMIN_RESOURCES_KEY,
|
|
3921
|
+
createResourceRegistry({
|
|
3922
|
+
http: options.http,
|
|
3923
|
+
// Read per call: the locale changes while the app runs.
|
|
3924
|
+
context: () => ({
|
|
3925
|
+
apiBase: endpoints.apiBase,
|
|
3926
|
+
projectKey: endpoints.projectKey,
|
|
3927
|
+
locale: i18n.locale.value
|
|
3928
|
+
}),
|
|
3929
|
+
resources: platformResources,
|
|
3930
|
+
overrides: options.resourceOverrides
|
|
3931
|
+
})
|
|
3932
|
+
);
|
|
3933
|
+
}
|
|
3415
3934
|
if (options.manifestGuard?.getManifest) {
|
|
3416
3935
|
app.provide(SUPER_ADMIN_MANIFEST_KEY, options.manifestGuard.getManifest);
|
|
3417
3936
|
}
|
|
@@ -3421,7 +3940,7 @@ function createSuperAdminApp(options) {
|
|
|
3421
3940
|
if (options.loginAdapter) {
|
|
3422
3941
|
app.provide(SUPER_ADMIN_LOGIN_ADAPTER_KEY, options.loginAdapter);
|
|
3423
3942
|
}
|
|
3424
|
-
app.provide(SUPER_ADMIN_HTTP_KEY,
|
|
3943
|
+
app.provide(SUPER_ADMIN_HTTP_KEY, http);
|
|
3425
3944
|
for (const plugin of options.installPlugins ?? []) {
|
|
3426
3945
|
plugin(app);
|
|
3427
3946
|
}
|
|
@@ -3443,6 +3962,10 @@ function createSuperAdminApp(options) {
|
|
|
3443
3962
|
SA_PORTAL_CLASS,
|
|
3444
3963
|
bindSaThemeToDocument,
|
|
3445
3964
|
createSuperAdminApp,
|
|
3965
|
+
quasarConfirm,
|
|
3966
|
+
quasarConfirmOptions,
|
|
3446
3967
|
quasarNotify,
|
|
3968
|
+
resolveQuasarOptions,
|
|
3969
|
+
useSuperAdminConfirm,
|
|
3447
3970
|
useSuperAdminNotify
|
|
3448
3971
|
});
|