@merkaly/nuxt 0.9.4 → 0.10.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/dist/module.json +1 -1
- package/dist/runtime/components/app.vue +5 -2
- package/dist/runtime/components/input/InputDropzone.d.vue.ts +60 -8
- package/dist/runtime/components/input/InputDropzone.vue +63 -61
- package/dist/runtime/components/input/InputDropzone.vue.d.ts +60 -8
- package/dist/runtime/composables/useApi.js +15 -7
- package/dist/runtime/composables/useTenant.d.ts +4 -1
- package/dist/runtime/composables/useTenant.js +6 -1
- package/dist/runtime/plugins/api.global.js +72 -60
- package/package.json +5 -5
package/dist/module.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { useRoute } from "#app";
|
|
3
|
-
import {
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
import { useAuth, useTenant, watchOnce, addRouteMiddleware, useNuxtApp } from "#imports";
|
|
4
5
|
import AuthMiddleware from "../middleware/auth";
|
|
5
6
|
import { useNavigation } from "../composables/useNavigation";
|
|
6
7
|
import AuthError from "./auth/AuthError.vue";
|
|
@@ -8,8 +9,10 @@ import { useColorMode } from "@vueuse/core";
|
|
|
8
9
|
import { Notivue, NotificationProgress, Notification, pastelTheme } from "notivue";
|
|
9
10
|
const $route = useRoute();
|
|
10
11
|
const { clear, error, isLoading, logout, retry } = useAuth();
|
|
12
|
+
const { fromServer } = useTenant();
|
|
11
13
|
const { hook } = useNuxtApp();
|
|
12
14
|
useColorMode({ attribute: "data-bs-theme" });
|
|
15
|
+
const blocking = computed(() => isLoading.value && !fromServer.value);
|
|
13
16
|
watchOnce(isLoading, () => AuthMiddleware($route, $route));
|
|
14
17
|
const { defer, regenerate } = useNavigation();
|
|
15
18
|
addRouteMiddleware("navigation", (to) => defer(to), { global: true });
|
|
@@ -27,7 +30,7 @@ hook("page:finish", () => regenerate());
|
|
|
27
30
|
</Notivue>
|
|
28
31
|
|
|
29
32
|
<BApp>
|
|
30
|
-
<slot v-if="
|
|
33
|
+
<slot v-if="blocking" name="loading" />
|
|
31
34
|
|
|
32
35
|
<slot v-else-if="error" :clear="clear" :error="error" :logout="logout" :retry="retry" name="error">
|
|
33
36
|
<AuthError :error="error" @logout="logout" @retry="retry" />
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
export interface InputDropzoneRejection {
|
|
2
|
+
file: File;
|
|
3
|
+
reason: 'type' | 'size';
|
|
4
|
+
}
|
|
5
|
+
declare function openDialog(): void;
|
|
6
|
+
declare var __VLS_1: {
|
|
7
|
+
dragging: boolean;
|
|
8
|
+
open: typeof openDialog;
|
|
9
|
+
};
|
|
10
|
+
type __VLS_Slots = {} & {
|
|
11
|
+
default?: (props: typeof __VLS_1) => any;
|
|
12
|
+
};
|
|
13
|
+
declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
2
14
|
accept: {
|
|
3
15
|
type: {
|
|
4
16
|
(arrayLength: number): string[];
|
|
@@ -18,15 +30,31 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
18
30
|
};
|
|
19
31
|
default: () => never[];
|
|
20
32
|
};
|
|
33
|
+
disabled: {
|
|
34
|
+
type: BooleanConstructor;
|
|
35
|
+
default: boolean;
|
|
36
|
+
};
|
|
37
|
+
hint: {
|
|
38
|
+
type: StringConstructor;
|
|
39
|
+
default: undefined;
|
|
40
|
+
};
|
|
41
|
+
maxSize: {
|
|
42
|
+
type: NumberConstructor;
|
|
43
|
+
default: number;
|
|
44
|
+
};
|
|
21
45
|
multiple: {
|
|
22
46
|
type: BooleanConstructor;
|
|
23
47
|
default: boolean;
|
|
24
48
|
};
|
|
25
|
-
|
|
26
|
-
type:
|
|
49
|
+
title: {
|
|
50
|
+
type: StringConstructor;
|
|
51
|
+
default: undefined;
|
|
27
52
|
};
|
|
28
|
-
}>, {
|
|
29
|
-
|
|
53
|
+
}>, {
|
|
54
|
+
open: typeof openDialog;
|
|
55
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
56
|
+
select: (files: File[]) => any;
|
|
57
|
+
reject: (rejections: InputDropzoneRejection[]) => any;
|
|
30
58
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
31
59
|
accept: {
|
|
32
60
|
type: {
|
|
@@ -47,18 +75,42 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
47
75
|
};
|
|
48
76
|
default: () => never[];
|
|
49
77
|
};
|
|
78
|
+
disabled: {
|
|
79
|
+
type: BooleanConstructor;
|
|
80
|
+
default: boolean;
|
|
81
|
+
};
|
|
82
|
+
hint: {
|
|
83
|
+
type: StringConstructor;
|
|
84
|
+
default: undefined;
|
|
85
|
+
};
|
|
86
|
+
maxSize: {
|
|
87
|
+
type: NumberConstructor;
|
|
88
|
+
default: number;
|
|
89
|
+
};
|
|
50
90
|
multiple: {
|
|
51
91
|
type: BooleanConstructor;
|
|
52
92
|
default: boolean;
|
|
53
93
|
};
|
|
54
|
-
|
|
55
|
-
type:
|
|
94
|
+
title: {
|
|
95
|
+
type: StringConstructor;
|
|
96
|
+
default: undefined;
|
|
56
97
|
};
|
|
57
98
|
}>> & Readonly<{
|
|
58
|
-
|
|
99
|
+
onSelect?: ((files: File[]) => any) | undefined;
|
|
100
|
+
onReject?: ((rejections: InputDropzoneRejection[]) => any) | undefined;
|
|
59
101
|
}>, {
|
|
102
|
+
title: string;
|
|
103
|
+
disabled: boolean;
|
|
60
104
|
accept: string[];
|
|
105
|
+
hint: string;
|
|
106
|
+
maxSize: number;
|
|
61
107
|
multiple: boolean;
|
|
62
108
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
109
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
63
110
|
declare const _default: typeof __VLS_export;
|
|
64
111
|
export default _default;
|
|
112
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
113
|
+
new (): {
|
|
114
|
+
$slots: S;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
@@ -1,80 +1,82 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { useDropZone, useFileDialog } from "@vueuse/core";
|
|
2
3
|
import { filesize } from "filesize";
|
|
4
|
+
import { computed, useTemplateRef } from "vue";
|
|
5
|
+
import { useI18n } from "vue-i18n";
|
|
3
6
|
const props = defineProps({
|
|
4
7
|
accept: { type: Array, default: () => [] },
|
|
5
|
-
|
|
8
|
+
// MIME types, e.g. image/png; empty accepts anything
|
|
9
|
+
disabled: { type: Boolean, default: false },
|
|
10
|
+
hint: { type: String, default: void 0 },
|
|
11
|
+
maxSize: { type: Number, default: 0 },
|
|
12
|
+
// bytes; 0 means no limit
|
|
13
|
+
multiple: { type: Boolean, default: false },
|
|
14
|
+
title: { type: String, default: void 0 }
|
|
6
15
|
});
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
16
|
+
const emit = defineEmits(["reject", "select"]);
|
|
17
|
+
const { t } = useI18n({ useScope: "local" });
|
|
18
|
+
const dropZone = useTemplateRef("dropZone");
|
|
19
|
+
const titleText = computed(() => props.title ?? t(props.multiple ? "titleMany" : "title"));
|
|
20
|
+
const hintText = computed(() => props.hint ?? (props.maxSize ? t("hint", { size: filesize(props.maxSize) }) : ""));
|
|
21
|
+
const { open, onChange } = useFileDialog({ accept: props.accept.join(","), multiple: props.multiple, reset: true });
|
|
22
|
+
onChange((files) => pick(files ? [...files] : []));
|
|
23
|
+
const { isOverDropZone } = useDropZone(dropZone, {
|
|
12
24
|
multiple: props.multiple,
|
|
13
|
-
onDrop:
|
|
14
|
-
preventDefaultForUnhandled:
|
|
25
|
+
onDrop: (files) => pick(files ?? []),
|
|
26
|
+
preventDefaultForUnhandled: true
|
|
15
27
|
});
|
|
16
|
-
function
|
|
17
|
-
|
|
28
|
+
function reasonOf(file) {
|
|
29
|
+
if (props.accept.length && !props.accept.includes(file.type)) return "type";
|
|
30
|
+
if (props.maxSize && file.size > props.maxSize) return "size";
|
|
18
31
|
}
|
|
19
|
-
function
|
|
20
|
-
|
|
32
|
+
function pick(files) {
|
|
33
|
+
if (props.disabled || !files.length) return;
|
|
34
|
+
const checked = (props.multiple ? files : files.slice(0, 1)).map((file) => ({ file, reason: reasonOf(file) }));
|
|
35
|
+
const rejections = checked.filter((it) => Boolean(it.reason));
|
|
36
|
+
const accepted = checked.filter((it) => !it.reason).map((it) => it.file);
|
|
37
|
+
if (rejections.length) emit("reject", rejections);
|
|
38
|
+
if (accepted.length) emit("select", accepted);
|
|
21
39
|
}
|
|
22
|
-
function
|
|
23
|
-
if (!
|
|
24
|
-
const images = [...files].map((it) => ({
|
|
25
|
-
alt: it.name,
|
|
26
|
-
id: crypto.randomUUID(),
|
|
27
|
-
name: it.name,
|
|
28
|
-
size: it.size,
|
|
29
|
-
url: URL.createObjectURL(it)
|
|
30
|
-
}));
|
|
31
|
-
return pictures.value.push(...images);
|
|
40
|
+
function openDialog() {
|
|
41
|
+
if (!props.disabled) open();
|
|
32
42
|
}
|
|
43
|
+
defineExpose({ open: openDialog });
|
|
33
44
|
</script>
|
|
34
45
|
|
|
35
46
|
<template>
|
|
36
|
-
<div
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
<div
|
|
48
|
+
ref="dropZone"
|
|
49
|
+
:class="{ 'dz-drag-hover': isOverDropZone, 'opacity-50 pe-none': props.disabled }"
|
|
50
|
+
class="dropzone dz-clickable text-start"
|
|
51
|
+
@click="openDialog()">
|
|
52
|
+
<slot :dragging="isOverDropZone" :open="openDialog">
|
|
53
|
+
<div class="dz-message needsclick">
|
|
54
|
+
<i class="ki-duotone ki-file-up text-primary fs-3x">
|
|
55
|
+
<span class="path1" />
|
|
56
|
+
<span class="path2" />
|
|
57
|
+
</i>
|
|
45
58
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<span class="path1" />
|
|
49
|
-
<span class="path2" />
|
|
50
|
-
</i>
|
|
59
|
+
<div class="ms-4">
|
|
60
|
+
<h3 class="fs-5 fw-bold text-gray-900 mb-1" v-text="titleText" />
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
<h3 class="fs-5 fw-bold text-gray-900 mb-1">Drop files here or click to upload.</h3>
|
|
54
|
-
<span class="fs-7 fw-semibold text-gray-400">Upload up to 10 files</span>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
<template v-for="picture in pictures" :key="picture._id">
|
|
59
|
-
<div class="dz-preview border dz-image-preview">
|
|
60
|
-
<div class="dz-image">
|
|
61
|
-
<BImg :alt="picture.alt" :src="picture.url" class="w-100" />
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
<div class="dz-details">
|
|
65
|
-
<div class="dz-size">
|
|
66
|
-
<span v-text="filesize(picture.size)" />
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
<div class="dz-filename">
|
|
70
|
-
<span v-text="picture.name" />
|
|
71
|
-
</div>
|
|
62
|
+
<span v-if="hintText" class="fs-7 fw-semibold text-gray-500" v-text="hintText" />
|
|
72
63
|
</div>
|
|
73
|
-
|
|
74
|
-
<span class="dz-remove" @click="removePicture(picture._id)">
|
|
75
|
-
<span>Remove file</span>
|
|
76
|
-
</span>
|
|
77
64
|
</div>
|
|
78
|
-
</
|
|
65
|
+
</slot>
|
|
79
66
|
</div>
|
|
80
67
|
</template>
|
|
68
|
+
|
|
69
|
+
<i18n lang="json">
|
|
70
|
+
{
|
|
71
|
+
"en": {
|
|
72
|
+
"hint": "Up to {size}",
|
|
73
|
+
"title": "Drop a file here or click to choose",
|
|
74
|
+
"titleMany": "Drop files here or click to choose"
|
|
75
|
+
},
|
|
76
|
+
"pt": {
|
|
77
|
+
"hint": "Até {size}",
|
|
78
|
+
"title": "Solte um arquivo aqui ou clique para escolher",
|
|
79
|
+
"titleMany": "Solte os arquivos aqui ou clique para escolher"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
</i18n>
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
export interface InputDropzoneRejection {
|
|
2
|
+
file: File;
|
|
3
|
+
reason: 'type' | 'size';
|
|
4
|
+
}
|
|
5
|
+
declare function openDialog(): void;
|
|
6
|
+
declare var __VLS_1: {
|
|
7
|
+
dragging: boolean;
|
|
8
|
+
open: typeof openDialog;
|
|
9
|
+
};
|
|
10
|
+
type __VLS_Slots = {} & {
|
|
11
|
+
default?: (props: typeof __VLS_1) => any;
|
|
12
|
+
};
|
|
13
|
+
declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
2
14
|
accept: {
|
|
3
15
|
type: {
|
|
4
16
|
(arrayLength: number): string[];
|
|
@@ -18,15 +30,31 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
18
30
|
};
|
|
19
31
|
default: () => never[];
|
|
20
32
|
};
|
|
33
|
+
disabled: {
|
|
34
|
+
type: BooleanConstructor;
|
|
35
|
+
default: boolean;
|
|
36
|
+
};
|
|
37
|
+
hint: {
|
|
38
|
+
type: StringConstructor;
|
|
39
|
+
default: undefined;
|
|
40
|
+
};
|
|
41
|
+
maxSize: {
|
|
42
|
+
type: NumberConstructor;
|
|
43
|
+
default: number;
|
|
44
|
+
};
|
|
21
45
|
multiple: {
|
|
22
46
|
type: BooleanConstructor;
|
|
23
47
|
default: boolean;
|
|
24
48
|
};
|
|
25
|
-
|
|
26
|
-
type:
|
|
49
|
+
title: {
|
|
50
|
+
type: StringConstructor;
|
|
51
|
+
default: undefined;
|
|
27
52
|
};
|
|
28
|
-
}>, {
|
|
29
|
-
|
|
53
|
+
}>, {
|
|
54
|
+
open: typeof openDialog;
|
|
55
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
56
|
+
select: (files: File[]) => any;
|
|
57
|
+
reject: (rejections: InputDropzoneRejection[]) => any;
|
|
30
58
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
31
59
|
accept: {
|
|
32
60
|
type: {
|
|
@@ -47,18 +75,42 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
47
75
|
};
|
|
48
76
|
default: () => never[];
|
|
49
77
|
};
|
|
78
|
+
disabled: {
|
|
79
|
+
type: BooleanConstructor;
|
|
80
|
+
default: boolean;
|
|
81
|
+
};
|
|
82
|
+
hint: {
|
|
83
|
+
type: StringConstructor;
|
|
84
|
+
default: undefined;
|
|
85
|
+
};
|
|
86
|
+
maxSize: {
|
|
87
|
+
type: NumberConstructor;
|
|
88
|
+
default: number;
|
|
89
|
+
};
|
|
50
90
|
multiple: {
|
|
51
91
|
type: BooleanConstructor;
|
|
52
92
|
default: boolean;
|
|
53
93
|
};
|
|
54
|
-
|
|
55
|
-
type:
|
|
94
|
+
title: {
|
|
95
|
+
type: StringConstructor;
|
|
96
|
+
default: undefined;
|
|
56
97
|
};
|
|
57
98
|
}>> & Readonly<{
|
|
58
|
-
|
|
99
|
+
onSelect?: ((files: File[]) => any) | undefined;
|
|
100
|
+
onReject?: ((rejections: InputDropzoneRejection[]) => any) | undefined;
|
|
59
101
|
}>, {
|
|
102
|
+
title: string;
|
|
103
|
+
disabled: boolean;
|
|
60
104
|
accept: string[];
|
|
105
|
+
hint: string;
|
|
106
|
+
maxSize: number;
|
|
61
107
|
multiple: boolean;
|
|
62
108
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
109
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
63
110
|
declare const _default: typeof __VLS_export;
|
|
64
111
|
export default _default;
|
|
112
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
113
|
+
new (): {
|
|
114
|
+
$slots: S;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { reactive, ref } from "vue";
|
|
2
|
-
import { useNuxtApp } from "#imports";
|
|
1
|
+
import { getCurrentInstance, onServerPrefetch, reactive, ref } from "vue";
|
|
2
|
+
import { useNuxtApp, useState } from "#imports";
|
|
3
3
|
function useApi(callback) {
|
|
4
4
|
const { $api } = useNuxtApp();
|
|
5
5
|
const controller = new AbortController();
|
|
@@ -11,13 +11,17 @@ function useApi(callback) {
|
|
|
11
11
|
return options;
|
|
12
12
|
};
|
|
13
13
|
const initialOptions = getOptions(params);
|
|
14
|
+
const nuxtApp = useNuxtApp();
|
|
15
|
+
const ssr = import.meta.server || nuxtApp.payload.serverRendered === true;
|
|
16
|
+
const key = `merkaly:api:${initialOptions.method}:${initialOptions.uri}:${JSON.stringify(initialOptions.query ?? {})}`;
|
|
14
17
|
const loading = ref(false);
|
|
15
|
-
const data = ref(initialOptions.default?.());
|
|
16
|
-
const meta = ref({});
|
|
18
|
+
const data = ssr ? useState(`${key}:data`, () => initialOptions.default?.()) : ref(initialOptions.default?.());
|
|
19
|
+
const meta = ssr ? useState(`${key}:meta`, () => ({})) : ref({});
|
|
20
|
+
const prefetched = ssr ? useState(`${key}:prefetched`, () => false) : ref(false);
|
|
17
21
|
const error = ref();
|
|
18
22
|
const execute = async (args = {}) => {
|
|
19
|
-
Object.keys(args).forEach((
|
|
20
|
-
params[
|
|
23
|
+
Object.keys(args).forEach((key2) => {
|
|
24
|
+
params[key2] = args[key2];
|
|
21
25
|
});
|
|
22
26
|
const currentOptions = getOptions(params);
|
|
23
27
|
await $api(currentOptions.uri, {
|
|
@@ -32,7 +36,11 @@ function useApi(callback) {
|
|
|
32
36
|
});
|
|
33
37
|
};
|
|
34
38
|
if (initialOptions.immediate) {
|
|
35
|
-
|
|
39
|
+
if (import.meta.server && getCurrentInstance()) {
|
|
40
|
+
onServerPrefetch(() => execute().then(() => void (prefetched.value = true)));
|
|
41
|
+
} else if (!(nuxtApp.isHydrating && prefetched.value)) {
|
|
42
|
+
void execute();
|
|
43
|
+
}
|
|
36
44
|
}
|
|
37
45
|
return {
|
|
38
46
|
abort: () => controller.abort(),
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export declare const useTenant: () => {
|
|
2
|
+
fromServer: import("vue").Ref<boolean, boolean>;
|
|
2
3
|
isReady: import("vue").ComputedRef<boolean>;
|
|
3
|
-
setTenant: (value: string
|
|
4
|
+
setTenant: (value: string, options?: {
|
|
5
|
+
server?: boolean;
|
|
6
|
+
}) => void;
|
|
4
7
|
tenant: import("vue").Ref<string, string>;
|
|
5
8
|
};
|
|
@@ -2,9 +2,14 @@ import { computed } from "vue";
|
|
|
2
2
|
import { useState } from "#imports";
|
|
3
3
|
export const useTenant = () => {
|
|
4
4
|
const tenant = useState("tenant:id", () => "");
|
|
5
|
+
const fromServer = useState("tenant:server", () => false);
|
|
5
6
|
const isReady = computed(() => Boolean(tenant.value));
|
|
6
|
-
const setTenant = (value) =>
|
|
7
|
+
const setTenant = (value, options = {}) => {
|
|
8
|
+
tenant.value = value;
|
|
9
|
+
if (options.server) fromServer.value = true;
|
|
10
|
+
};
|
|
7
11
|
return {
|
|
12
|
+
fromServer,
|
|
8
13
|
isReady,
|
|
9
14
|
setTenant,
|
|
10
15
|
tenant
|
|
@@ -1,66 +1,78 @@
|
|
|
1
|
-
import { defineNuxtPlugin, useRuntimeConfig } from "#app";
|
|
1
|
+
import { defineNuxtPlugin, useRequestURL, useRuntimeConfig } from "#app";
|
|
2
2
|
import { useAuth, useTenant } from "#imports";
|
|
3
3
|
const GLOBAL_API_HEADER = "x-merkaly-global";
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
function serverBaseURL(api, global) {
|
|
5
|
+
const prefix = global ? "" : (api.prefix ?? "").replace(/^\/+|\/+$/g, "");
|
|
6
|
+
return prefix ? `${api.url.replace(/\/+$/, "")}/${prefix}` : api.url;
|
|
7
|
+
}
|
|
8
|
+
export default defineNuxtPlugin(({ provide }) => {
|
|
9
|
+
const requestHost = import.meta.server ? useRequestURL().host : void 0;
|
|
10
|
+
if (import.meta.server && import.meta.dev && new URL(useRuntimeConfig().public.merkaly.api.url).hostname.endsWith(".test")) {
|
|
11
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
12
|
+
}
|
|
13
|
+
provide("api", async (url, options = {}) => {
|
|
14
|
+
const { token } = useAuth();
|
|
15
|
+
const { tenant } = useTenant();
|
|
16
|
+
const { public: { merkaly } } = useRuntimeConfig();
|
|
17
|
+
const requiresTenant = options.requiresTenant ?? merkaly.auth0.requiresTenant;
|
|
18
|
+
if (!tenant.value && !options.global && requiresTenant) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`[api] Tenant context is required but not set for: ${url}
|
|
12
21
|
This surface has requiresTenant: true. Possible causes:
|
|
13
22
|
1. The request fired before the auth boot sequence completed (race condition).
|
|
14
23
|
2. setTenant() was never called in this surface's auth plugin.
|
|
15
24
|
3. The adapter should use { global: true } to bypass tenant enforcement.`
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
options
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
await $fetch(url, {
|
|
28
|
+
baseURL: import.meta.server ? serverBaseURL(merkaly.api, options.global) : "/api",
|
|
29
|
+
body: options?.body,
|
|
30
|
+
headers: {
|
|
31
|
+
authorization: token.value ? `Bearer ${token.value}` : "",
|
|
32
|
+
identity: tenant.value,
|
|
33
|
+
[GLOBAL_API_HEADER]: options.global ? "true" : "",
|
|
34
|
+
...requestHost ? { "x-merkaly-host": requestHost } : {},
|
|
35
|
+
// tenant host, as the proxy sends it
|
|
36
|
+
...options?.headers
|
|
37
|
+
},
|
|
38
|
+
method: options?.method,
|
|
39
|
+
onRequest: async () => {
|
|
40
|
+
if (options.data) options.data.value = options.default?.();
|
|
41
|
+
if (options.meta) options.meta.value = {};
|
|
42
|
+
if (options.error) options.error.value = void 0;
|
|
43
|
+
if (options.loading) options.loading.value = true;
|
|
44
|
+
const result = await Promise.resolve(options?.onBeforeSend?.({ body: options.body, headers: options.headers, query: options.query })).then((res) => res).catch(() => false);
|
|
45
|
+
if (result === false) {
|
|
46
|
+
options.controller?.abort?.("Request aborted by onBeforeSend");
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
onRequestError: async ({ error, response, request }) => {
|
|
50
|
+
await options.onFatal?.(error);
|
|
51
|
+
if (options.error) options.error.value = error;
|
|
52
|
+
await options?.onComplete?.({ response, request });
|
|
53
|
+
if (options.loading) options.loading.value = false;
|
|
54
|
+
},
|
|
55
|
+
onResponse: async ({ response, request }) => {
|
|
56
|
+
const { status, _data, headers } = response;
|
|
57
|
+
const { data, meta } = _data || {};
|
|
58
|
+
await options?.onResponse?.({ response, request });
|
|
59
|
+
if (status >= 400) return;
|
|
60
|
+
await options?.onSuccess?.({ data, meta, headers, params: options.params });
|
|
61
|
+
if (options.data) options.data.value = data;
|
|
62
|
+
if (options.meta) options.meta.value = meta;
|
|
63
|
+
await options?.onComplete?.({ response, request });
|
|
64
|
+
if (options.loading) options.loading.value = false;
|
|
65
|
+
},
|
|
66
|
+
onResponseError: async ({ response, request }) => {
|
|
67
|
+
const { _data } = response;
|
|
68
|
+
await options.onError?.(_data, { response, request });
|
|
69
|
+
if (options.error) options.error.value = _data;
|
|
70
|
+
await options?.onComplete?.({ response, request });
|
|
71
|
+
if (options.loading) options.loading.value = false;
|
|
72
|
+
},
|
|
73
|
+
query: options?.query,
|
|
74
|
+
retry: false,
|
|
75
|
+
signal: options.controller?.signal
|
|
76
|
+
}).catch((reason) => reason);
|
|
77
|
+
});
|
|
78
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@merkaly/nuxt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/merkaly-io/nuxt.git"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@auth0/auth0-spa-js": "^2.
|
|
31
|
+
"@auth0/auth0-spa-js": "^2.30.0",
|
|
32
32
|
"@bootstrap-vue-next/nuxt": "^1.0.0",
|
|
33
33
|
"@nuxt/devtools": "^4.0.0",
|
|
34
34
|
"@nuxt/eslint": "1.17.0",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@nuxt/kit": "^4.6.0",
|
|
39
39
|
"@nuxtjs/i18n": "^10.6.0",
|
|
40
40
|
"@nuxtjs/plausible": "^4.0.0",
|
|
41
|
-
"@sentry/nuxt": "^11.
|
|
41
|
+
"@sentry/nuxt": "^11.10.0",
|
|
42
42
|
"@types/node": "latest",
|
|
43
43
|
"@types/vue-select": "^3.16.8",
|
|
44
44
|
"@vueuse/components": "^15.0.0",
|
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
"change-case": "^5.4.4",
|
|
51
51
|
"class-transformer": "^0.5.1",
|
|
52
52
|
"class-validator": "^0.15.1",
|
|
53
|
-
"eslint": "^10.
|
|
53
|
+
"eslint": "^10.14.0",
|
|
54
54
|
"filesize": "^11.0.17",
|
|
55
55
|
"gsap": "^3.15.0",
|
|
56
56
|
"notivue": "^2.4.5",
|
|
57
57
|
"nuxt": "^4.6.3",
|
|
58
58
|
"reflect-metadata": "^0.2.2",
|
|
59
|
-
"sass": "^1.
|
|
59
|
+
"sass": "^1.107.1",
|
|
60
60
|
"typescript": "~5.9.3",
|
|
61
61
|
"v-money3": "^3.26.1",
|
|
62
62
|
"vite-svg-loader": "^5.1.1",
|