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