@fy-/fws-vue 0.0.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.
@@ -0,0 +1,305 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted } from "vue";
3
+ import type { KlbFlowData, KlbUserFlowField } from "@fy-/fws-types";
4
+ import { useRoute, useRouter } from "vue-router";
5
+ import { useTranslation } from "../../translations";
6
+ import { useEventBus } from "../../event-bus";
7
+ import { useUserStore } from "../../stores/user";
8
+ import { useRest, APIResult } from "../../rest";
9
+ import DefaultInput from "../ui/DefaultInput.vue";
10
+ import { ClientOnly } from "../ssr/ClientOnly";
11
+ const rest = useRest();
12
+ interface UserFlow extends APIResult {
13
+ data: KlbFlowData;
14
+ }
15
+
16
+ const props = withDefaults(
17
+ defineProps<{
18
+ returnDefault?: string;
19
+ forceAction?: string;
20
+ onSuccess?: Function;
21
+ }>(),
22
+ {
23
+ mode: "klb",
24
+ returnDefault: "/",
25
+ },
26
+ );
27
+ const isExternalUrl = (url: string) => {
28
+ return url.startsWith("http://") || url.startsWith("https://");
29
+ };
30
+ type paramsType = {
31
+ initial: boolean;
32
+ oauth?: string;
33
+ };
34
+ const store = useUserStore();
35
+ const route = useRoute();
36
+ const router = useRouter();
37
+ const eventBus = useEventBus();
38
+ const returnTo = ref<string>(props.returnDefault);
39
+ const responseMessage = ref<string | null>(null);
40
+ const responseError = ref<APIResult>();
41
+ const responseReq = ref<string[]>([]);
42
+ const responseFields = ref<Array<KlbUserFlowField>>([]);
43
+ const response = ref<UserFlow>();
44
+ const hasOauth = ref<boolean>(false);
45
+ const fieldsError = ref<Record<string, any>>({});
46
+ const pwdRecoverMailSent = ref<boolean>(false);
47
+ const inputs = ref<InstanceType<typeof DefaultInput>[]>([]);
48
+ const translate = useTranslation();
49
+
50
+ const formData = ref<Record<string, any>>({
51
+ return_to: props.returnDefault,
52
+ session: null,
53
+ action: props.forceAction ? props.forceAction : undefined,
54
+ });
55
+ const completed = ref(false);
56
+
57
+ const doTrigger = async (field: any) => {
58
+ // eslint-disable-next-line
59
+
60
+ const _res = await eval(
61
+ `const _rest = rest; ${field.info.Button_Extra.trigger}`,
62
+ );
63
+ if (_res.ethereum) {
64
+ formData.value.ethereum = _res.ethereum;
65
+ responseReq.value = [];
66
+ await userFlow();
67
+ }
68
+ };
69
+ const userFlow = async (params: paramsType = { initial: false }) => {
70
+ eventBus.emit("login-loading", true);
71
+ fieldsError.value = {};
72
+ responseError.value = undefined;
73
+
74
+ if (params.initial === false) {
75
+ let hasError = false;
76
+ responseReq.value.forEach((field) => {
77
+ if (!formData.value[field] || formData.value[field] == "") {
78
+ fieldsError.value[field] = translate("vuelidate_validator_req");
79
+ hasError = true;
80
+ }
81
+ });
82
+ if (hasError) {
83
+ eventBus.emit("login-loading", false);
84
+ return;
85
+ }
86
+ }
87
+ hasOauth.value = false;
88
+
89
+ if (params.oauth) {
90
+ formData.value.oauth2 = params.oauth;
91
+ }
92
+
93
+ if (route.query.return_to && typeof route.query.return_to == "string") {
94
+ returnTo.value = route.query.return_to
95
+ ? route.query.return_to
96
+ : props.returnDefault;
97
+ }
98
+
99
+ if (!formData.value.session) {
100
+ formData.value.session = route.query.session
101
+ ? route.query.session
102
+ : undefined;
103
+ }
104
+
105
+ formData.value.return_to = returnTo.value;
106
+ response.value = (await rest("User:flow", "POST", formData.value).catch(
107
+ (err: APIResult) => {
108
+ if (err.token && err.token == "invalid_request_token") {
109
+ window.location.reload();
110
+ }
111
+ responseError.value = err;
112
+ if (responseError.value.param) {
113
+ fieldsError.value[responseError.value.param] =
114
+ responseError.value.token;
115
+ }
116
+ eventBus.emit("login-loading", false);
117
+ return;
118
+ },
119
+ )) as UserFlow;
120
+ if (response.value?.result == "success") {
121
+ if (props.onSuccess) {
122
+ await props.onSuccess();
123
+ }
124
+ if (response.value.data.complete == true && response.value.data.user) {
125
+ store.setUser(response.value.data.user);
126
+ if (isExternalUrl(returnTo.value)) {
127
+ window.location.href = returnTo.value;
128
+ } else {
129
+ const routeExists = router.resolve(returnTo.value);
130
+ if (routeExists.matched.length != 0) router.push(returnTo.value);
131
+ else window.location.href = returnTo.value;
132
+ }
133
+ return;
134
+ }
135
+ if (response.value.data.url) {
136
+ window.location.href = response.value.data.url;
137
+ return;
138
+ }
139
+ if (response.value.data.Redirect && response.value.data.complete) {
140
+ router.push("/");
141
+ return;
142
+ }
143
+ formData.value = {
144
+ session: response.value.data.session,
145
+ };
146
+ inputs.value = [];
147
+ responseFields.value = response.value.data.fields;
148
+ if (response.value.data.message)
149
+ responseMessage.value = response.value.data.message;
150
+ responseReq.value = response.value.data.req;
151
+ responseFields.value.forEach((field) => {
152
+ if (field.type == "oauth2") {
153
+ hasOauth.value = true;
154
+ }
155
+ });
156
+ setTimeout(() => {
157
+ if (inputs.value.length > 0 && inputs.value[inputs.value.length - 1])
158
+ inputs.value[inputs.value.length - 1].focus();
159
+ }, 300);
160
+ } else {
161
+ //
162
+ }
163
+
164
+ eventBus.emit("login-loading", false);
165
+ };
166
+
167
+ onMounted(async () => {
168
+ await userFlow({ initial: true });
169
+ });
170
+ </script>
171
+ <template>
172
+ <ClientOnly>
173
+ <form
174
+ @submit.prevent="userFlow()"
175
+ v-if="!completed"
176
+ class="fws-login w-full"
177
+ >
178
+ <!--<FyLoader id="klblogin" />-->
179
+ <div class="w-full">
180
+ <h2
181
+ class="text-lg text-fv-neutral-700 dark:text-fv-neutral-300"
182
+ v-if="responseMessage"
183
+ >
184
+ {{ responseMessage }}
185
+ </h2>
186
+ <template v-if="responseFields && responseFields.length > 0">
187
+ <template v-for="field of responseFields" :key="field.label">
188
+ <h3
189
+ v-if="field.type == 'label'"
190
+ class="pt-2 pb-1 text-sm text-fv-neutral-500 dark:text-fv-neutral-400"
191
+ :class="
192
+ field.style == 'error'
193
+ ? 'text-sm my-2 p-1 font-semibold text-red-800 dark:text-red-300'
194
+ : ''
195
+ "
196
+ >
197
+ <a :href="field.link" v-if="field.link" class="fws-link mb-3">{{
198
+ field.label
199
+ }}</a>
200
+ <span class="mb-2" v-else>{{ field.label }}</span>
201
+ </h3>
202
+
203
+ <template v-if="field.cat == 'input'">
204
+ <template
205
+ v-if="
206
+ field.type == 'text' ||
207
+ field.type == 'password' ||
208
+ field.type == 'email'
209
+ "
210
+ >
211
+ <DefaultInput
212
+ v-if="field.name"
213
+ :id="field.name"
214
+ :label="field.label"
215
+ class="mt-3"
216
+ :placeholder="field.name == 'name' ? 'John Doe' : field.label"
217
+ :error="fieldsError[field.name]"
218
+ :type="field.type"
219
+ ref="inputs"
220
+ v-model="formData[field.name]"
221
+ :req="responseReq.includes(field.name)"
222
+ />
223
+ </template>
224
+ </template>
225
+ <template v-if="field.type == 'checkbox'">
226
+ <DefaultInput
227
+ v-if="field.name"
228
+ :id="field.name"
229
+ class="mt-3"
230
+ :label="field.label"
231
+ :error="fieldsError[field.name]"
232
+ :type="field.type"
233
+ v-model:checkbox-value="formData[field.name]"
234
+ :req="responseReq.includes(field.name)"
235
+ :link-icon="field.link"
236
+ />
237
+ </template>
238
+ </template>
239
+ <div
240
+ class="text-sm my-2 p-1 font-semibold text-red-800 dark:text-red-300"
241
+ v-if="responseError && responseError.token"
242
+ >
243
+ {{ $t(responseError.token) }}
244
+ </div>
245
+ <div v-if="responseReq.includes('password') && 0" class="reset-pwd">
246
+ <a
247
+ href="javascript:void(0)"
248
+ @click="
249
+ () => {
250
+ eventBus.emit('ResetPasswordModal', true);
251
+ pwdRecoverMailSent = false;
252
+ }
253
+ "
254
+ >{{ $t("recover_pwd_link") }}</a
255
+ >
256
+ </div>
257
+ <button class="btn primary medium mt-4">
258
+ {{ $t("cta_login_next") }}
259
+ </button>
260
+ <template v-if="hasOauth">
261
+ <div
262
+ class="relative flex items-center justify-center w-full mt-4 mb-2"
263
+ >
264
+ <div
265
+ class="h-px bg-neutral-300 dark:bg-neutral-600 inset-x-0 absolute"
266
+ ></div>
267
+ <div
268
+ class="bg-white dark:bg-neutral-700 fws-helper-text px-4 relative"
269
+ >
270
+ {{ $t("or_text_label") }}
271
+ </div>
272
+ </div>
273
+ <div
274
+ class="flex items-center justify-center shadow py-2 rounded bg-fv-neutral-50 dark:bg-fv-neutral-800"
275
+ >
276
+ <template v-for="field of responseFields" :key="field.id">
277
+ <a
278
+ @click="
279
+ () => {
280
+ if (field.info.Button_Extra?.trigger) {
281
+ doTrigger(field);
282
+ } else {
283
+ userFlow({ initial: true, oauth: field.id });
284
+ }
285
+ }
286
+ "
287
+ v-if="field.type && field.type == 'oauth2' && field.button"
288
+ href="javascript:void(0);"
289
+ >
290
+ <img
291
+ :key="`${field.label}oauth`"
292
+ class="h-12 w-12 block p-2 mr-3 rounded-full border-4 shadow hover:border"
293
+ :alt="field.info.Name"
294
+ :src="field.button.logo"
295
+ :style="`background: ${field.button['background-color']}`"
296
+ />
297
+ </a>
298
+ </template>
299
+ </div>
300
+ </template>
301
+ </template>
302
+ </div>
303
+ </form>
304
+ </ClientOnly>
305
+ </template>
@@ -0,0 +1,12 @@
1
+ import { ref, onMounted, defineComponent } from "vue";
2
+
3
+ export const ClientOnly = defineComponent({
4
+ __name: "ClientOnly",
5
+ setup(_, { slots }) {
6
+ const show = ref(false);
7
+ onMounted(() => {
8
+ show.value = true;
9
+ });
10
+ return () => (show.value && slots.default ? slots.default() : null);
11
+ },
12
+ });
@@ -0,0 +1,75 @@
1
+ <script lang="ts" setup>
2
+ import { HomeIcon, ChevronRightIcon } from "@heroicons/vue/24/solid";
3
+ import type { BreadcrumbLink } from "../../types";
4
+ withDefaults(
5
+ defineProps<{
6
+ nav: BreadcrumbLink[];
7
+ showHome: Boolean;
8
+ }>(),
9
+ {
10
+ nav: () => [],
11
+ showHome: () => true,
12
+ },
13
+ );
14
+ </script>
15
+ <template>
16
+ <ol
17
+ class="inline-flex items-center flex-wrap"
18
+ itemscope
19
+ itemtype="https://schema.org/BreadcrumbList"
20
+ >
21
+ <template v-for="(item, index) in nav" :key="`bc_${index.toString()}`">
22
+ <li class="'inline-flex items-center'">
23
+ <ChevronRightIcon
24
+ v-if="index != 0"
25
+ :class="
26
+ item.to
27
+ ? index == 0
28
+ ? 'w-4 h-4 mr-2 inline-block'
29
+ : 'w-5 h-5 text-fv-neutral-400 inline-block mx-0.5 md:mx-1.5'
30
+ : 'w-5 h-5 text-fv-neutral-400 inline-block mx-0.5 md:mx-1.5'
31
+ "
32
+ />
33
+
34
+ <router-link
35
+ :to="item.to"
36
+ v-if="item.to"
37
+ itemprop="itemListElement"
38
+ itemtype="https://schema.org/ListItem"
39
+ itemscope
40
+ :class="
41
+ item.to
42
+ ? index == 0
43
+ ? 'text-xs font-medium text-fv-neutral-700 hover:text-fv-neutral-900 dark:text-fv-neutral-200 dark:hover:text-white'
44
+ : 'text-xs font-medium text-fv-neutral-700 hover:text-fv-neutral-900 dark:text-fv-neutral-200 dark:hover:text-white'
45
+ : ''
46
+ "
47
+ >
48
+ <HomeIcon
49
+ :class="
50
+ item.to
51
+ ? index == 0
52
+ ? 'w-4 h-4 mr-2 inline-block'
53
+ : 'w-4 h-4 text-fv-neutral-400 inline-block mx-0.5 md:mx-1.5'
54
+ : 'w-4 h-4 text-fv-neutral-400 inline-block mx-0.5 md:mx-1.5'
55
+ "
56
+ v-if="showHome && index === 0"
57
+ />
58
+ <span itemprop="name">{{ item.name }}</span>
59
+ <meta itemprop="position" :content="(index + 1).toString()" />
60
+ <meta itemprop="url" :content="item.to" />
61
+ </router-link>
62
+ <span
63
+ class="text-xs font-medium text-fv-neutral-500 dark:text-fv-neutral-200"
64
+ v-else
65
+ itemprop="itemListElement"
66
+ itemtype="https://schema.org/ListItem"
67
+ itemscope
68
+ >
69
+ <span itemprop="name">{{ item.name }}</span>
70
+ <meta itemprop="position" :content="(index + 1).toString()" />
71
+ </span>
72
+ </li>
73
+ </template>
74
+ </ol>
75
+ </template>
@@ -0,0 +1,69 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted, onUnmounted } from "vue";
3
+ import { ExclamationCircleIcon } from "@heroicons/vue/24/solid";
4
+ import { useEventBus } from "../../event-bus";
5
+ import DefaultModal from "./DefaultModal.vue";
6
+
7
+ const eventBus = useEventBus();
8
+ const title = ref<string | null>(null);
9
+ const desc = ref<string | null>(null);
10
+ const onConfirm = ref<Function | null>(null);
11
+ interface ConfirmModalData {
12
+ title: string;
13
+ desc: string;
14
+ onConfirm: Function;
15
+ }
16
+ const _onConfirm = async () => {
17
+ if (onConfirm.value) {
18
+ await onConfirm.value();
19
+ }
20
+ resetConfirm();
21
+ };
22
+ const resetConfirm = () => {
23
+ title.value = null;
24
+ desc.value = null;
25
+ onConfirm.value = null;
26
+ eventBus.emit("confirmModal", false);
27
+ };
28
+ const showConfirm = (data: ConfirmModalData) => {
29
+ title.value = data.title;
30
+ desc.value = data.desc;
31
+ onConfirm.value = data.onConfirm;
32
+ eventBus.emit("confirmModal", true);
33
+ };
34
+
35
+ onMounted(() => {
36
+ eventBus.on("resetConfirm", resetConfirm);
37
+ eventBus.on("showConfirm", showConfirm);
38
+ });
39
+ onUnmounted(() => {
40
+ eventBus.off("resetConfirm", resetConfirm);
41
+ eventBus.off("showConfirm", showConfirm);
42
+ });
43
+ </script>
44
+ <template>
45
+ <DefaultModal id="confirm" :title="title ? title : ''">
46
+ <div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
47
+ <div class="p-6 text-center">
48
+ <ExclamationCircleIcon
49
+ class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200"
50
+ />
51
+
52
+ <h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">
53
+ {{ desc ? desc : title }}
54
+ </h3>
55
+ <div class="flex justify-between gap-3 mt-4">
56
+ <button class="btn danger defaults" @click="_onConfirm()">
57
+ {{ $t("confirm_modal_cta_confirm") }}
58
+ </button>
59
+ <button
60
+ class="btn neutral defaults"
61
+ @click="$eventBus.emit('confirmModal', false)"
62
+ >
63
+ {{ $t("confirm_modal_cta_cancel") }}
64
+ </button>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </DefaultModal>
69
+ </template>
@@ -0,0 +1,56 @@
1
+ <script lang="ts" setup>
2
+ import { computed } from "vue";
3
+ import { DefaultInput } from "../..";
4
+ interface DateInterval {
5
+ $between: [any, any];
6
+ }
7
+ const props = withDefaults(
8
+ defineProps<{
9
+ mode?: "interval" | "single";
10
+ modelValue?: DateInterval;
11
+ id: string;
12
+ label?: string;
13
+ }>(),
14
+ {
15
+ mode: "single",
16
+ modelValue: () => {
17
+ return { $between: [undefined, undefined] };
18
+ },
19
+ },
20
+ );
21
+
22
+ const emit = defineEmits(["update:modelValue"]);
23
+
24
+ const model = computed({
25
+ get: () => props.modelValue,
26
+ set: (items) => {
27
+ emit("update:modelValue", items);
28
+ },
29
+ });
30
+ </script>
31
+ <template>
32
+ <div v-if="mode == 'interval' && model">
33
+ <div class="flex flex-col md:flex-row">
34
+ <DefaultInput
35
+ v-model="model.$between[0]"
36
+ type="date"
37
+ :id="`${id}_start`"
38
+ class="w-full"
39
+ :label="`${label} (${$t('date_selection_start')})`"
40
+ />
41
+ <div class="md:mx-2 flex items-center justify-center">
42
+ <div>↭</div>
43
+ </div>
44
+ <DefaultInput
45
+ v-model="model.$between[1]"
46
+ type="date"
47
+ class="w-full"
48
+ :id="`${id}_end`"
49
+ :label="`${label} (${$t('date_selection_end')})`"
50
+ />
51
+ </div>
52
+ </div>
53
+ <div v-else>
54
+ <DefaultInput v-model="model.$between[0]" type="date" :id="id" />
55
+ </div>
56
+ </template>