@hostlink/nuxt-light 1.73.2 → 1.74.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/L/App.vue +0 -19
- package/dist/runtime/components/L/AppMain.d.vue.ts +3 -3
- package/dist/runtime/components/L/AppMain.vue +43 -10
- package/dist/runtime/components/L/AppMain.vue.d.ts +3 -3
- package/dist/runtime/components/L/Card.vue +3 -2
- package/dist/runtime/components/L/FacebookButton.d.vue.ts +19 -3
- package/dist/runtime/components/L/FacebookButton.vue +73 -12
- package/dist/runtime/components/L/FacebookButton.vue.d.ts +19 -3
- package/dist/runtime/components/L/FavMenu.d.vue.ts +2 -0
- package/dist/runtime/components/L/FavMenu.vue +10 -2
- package/dist/runtime/components/L/FavMenu.vue.d.ts +2 -0
- package/dist/runtime/components/L/Form.d.vue.ts +29 -56
- package/dist/runtime/components/L/Form.vue +70 -80
- package/dist/runtime/components/L/Form.vue.d.ts +29 -56
- package/dist/runtime/components/L/FormLayout.d.vue.ts +36 -0
- package/dist/runtime/components/L/FormLayout.vue +36 -0
- package/dist/runtime/components/L/FormLayout.vue.d.ts +36 -0
- package/dist/runtime/components/L/Login.vue +1 -1
- package/dist/runtime/components/L/Menu.d.vue.ts +2 -0
- package/dist/runtime/components/L/Menu.vue +4 -4
- package/dist/runtime/components/L/Menu.vue.d.ts +2 -0
- package/dist/runtime/composables/useLight.d.ts +0 -4
- package/dist/runtime/formkit/Form.d.vue.ts +2 -2
- package/dist/runtime/formkit/Form.vue +15 -18
- package/dist/runtime/formkit/Form.vue.d.ts +2 -2
- package/dist/runtime/formkit/index.js +2 -0
- package/dist/runtime/locales/en.json +8 -2
- package/dist/runtime/locales/zh-hk.json +8 -2
- package/dist/runtime/pages/Role/[name]/update-child.vue +4 -1
- package/dist/runtime/pages/System/database/table.vue +40 -2
- package/dist/runtime/pages/System/fs.vue +116 -15
- package/dist/runtime/pages/User/setting/index.vue +29 -12
- package/dist/runtime/pages/User/setting/information.vue +4 -28
- package/dist/runtime/pages/User/setting/open_id.vue +132 -91
- package/dist/runtime/pages/User/setting/two-factor-auth.vue +388 -143
- package/dist/runtime/pages/User/setting.vue +3 -4
- package/dist/runtime/types/form.d.ts +2 -0
- package/dist/runtime/types/form.js +0 -0
- package/dist/runtime/utils/filterMenuItems.d.ts +10 -0
- package/dist/runtime/utils/filterMenuItems.js +34 -0
- package/dist/runtime/utils/formLayout.d.ts +2 -0
- package/dist/runtime/utils/formLayout.js +7 -0
- package/dist/runtime/utils/socialSdk.d.ts +2 -0
- package/dist/runtime/utils/socialSdk.js +127 -0
- package/package.json +14 -14
- package/dist/runtime/pages/Role/add2.d.vue.ts +0 -3
- package/dist/runtime/pages/Role/add2.vue +0 -57
- package/dist/runtime/pages/Role/add2.vue.d.ts +0 -3
- package/dist/runtime/pages/System/test.d.vue.ts +0 -3
- package/dist/runtime/pages/System/test.vue +0 -13
- package/dist/runtime/pages/System/test.vue.d.ts +0 -3
|
@@ -6,17 +6,34 @@ const $q = useQuasar();
|
|
|
6
6
|
const { data: app, refresh } = await useAsyncData(async () => {
|
|
7
7
|
return await q({
|
|
8
8
|
app: {
|
|
9
|
+
listFileSystem: true,
|
|
9
10
|
fs: {
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
types: true,
|
|
12
|
+
decoratorTypes: true
|
|
12
13
|
},
|
|
13
14
|
driveTypes: true
|
|
14
15
|
}
|
|
15
16
|
}).then((res) => res.app);
|
|
16
17
|
});
|
|
17
|
-
const items = computed(() => app.value
|
|
18
|
+
const items = computed(() => app.value?.listFileSystem ?? []);
|
|
18
19
|
const dialog = ref(false);
|
|
19
20
|
const value = ref({});
|
|
21
|
+
const clone = (data) => JSON.parse(JSON.stringify(data));
|
|
22
|
+
const openAdd = () => {
|
|
23
|
+
value.value = {
|
|
24
|
+
data: {},
|
|
25
|
+
decorators: []
|
|
26
|
+
};
|
|
27
|
+
dialog.value = true;
|
|
28
|
+
};
|
|
29
|
+
const openEdit = (row) => {
|
|
30
|
+
value.value = clone({
|
|
31
|
+
...row,
|
|
32
|
+
data: row.data ?? {},
|
|
33
|
+
decorators: row.decorators ?? []
|
|
34
|
+
});
|
|
35
|
+
dialog.value = true;
|
|
36
|
+
};
|
|
20
37
|
const onSubmit = async (data) => {
|
|
21
38
|
loading.value = true;
|
|
22
39
|
try {
|
|
@@ -39,19 +56,19 @@ const onSubmit = async (data) => {
|
|
|
39
56
|
}
|
|
40
57
|
loading.value = false;
|
|
41
58
|
};
|
|
42
|
-
const onRemove = async (
|
|
59
|
+
const onRemove = async (rows) => {
|
|
43
60
|
$q.dialog({
|
|
44
61
|
title: "Confirm",
|
|
45
62
|
message: "Are you sure you want to delete this file system?",
|
|
46
63
|
ok: "Yes",
|
|
47
64
|
cancel: "No"
|
|
48
65
|
}).onOk(async () => {
|
|
49
|
-
for (let row of
|
|
66
|
+
for (let row of rows) {
|
|
50
67
|
await m("lightFSdelete", {
|
|
51
68
|
uuid: row.uuid
|
|
52
69
|
});
|
|
53
70
|
}
|
|
54
|
-
|
|
71
|
+
selected.value = [];
|
|
55
72
|
refresh();
|
|
56
73
|
});
|
|
57
74
|
};
|
|
@@ -88,6 +105,11 @@ const columns = [
|
|
|
88
105
|
name: "data",
|
|
89
106
|
label: "Data",
|
|
90
107
|
align: "left"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "decorators",
|
|
111
|
+
label: "Decorators",
|
|
112
|
+
align: "left"
|
|
91
113
|
}
|
|
92
114
|
];
|
|
93
115
|
const selected = ref([]);
|
|
@@ -96,12 +118,28 @@ const getType = (type) => {
|
|
|
96
118
|
if (!type) return null;
|
|
97
119
|
return app.value.fs.types.find((t) => t.name == type);
|
|
98
120
|
};
|
|
121
|
+
const getDecoratorType = (type) => {
|
|
122
|
+
if (!type) return null;
|
|
123
|
+
return app.value.fs.decoratorTypes.find((t) => t.name == type);
|
|
124
|
+
};
|
|
125
|
+
const isSecret = (key) => [
|
|
126
|
+
"secret_key",
|
|
127
|
+
"secretKey",
|
|
128
|
+
"password",
|
|
129
|
+
"token",
|
|
130
|
+
"access_key_secret"
|
|
131
|
+
].includes(key);
|
|
132
|
+
const displayValue = (key, optionValue) => {
|
|
133
|
+
if (isSecret(key) && optionValue) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
134
|
+
if (typeof optionValue === "boolean") return optionValue ? "Yes" : "No";
|
|
135
|
+
return optionValue;
|
|
136
|
+
};
|
|
99
137
|
</script>
|
|
100
138
|
|
|
101
139
|
<template>
|
|
102
140
|
<l-page title="File system">
|
|
103
141
|
<q-dialog v-model="dialog" persistent>
|
|
104
|
-
<q-card style="width:
|
|
142
|
+
<q-card style="width: 720px; max-width: 90vw;">
|
|
105
143
|
|
|
106
144
|
<q-toolbar>
|
|
107
145
|
<q-toolbar-title v-if="value.uuid">{{ $t('Edit File System') }}</q-toolbar-title>
|
|
@@ -116,15 +154,60 @@ const getType = (type) => {
|
|
|
116
154
|
option-value="name"></form-kit>
|
|
117
155
|
|
|
118
156
|
<form-kit type="group" name="data" v-if="value.type">
|
|
119
|
-
<template v-for="(option, name) in getType(value.type)
|
|
120
|
-
<form-kit type="l-
|
|
157
|
+
<template v-for="(option, name) in getType(value.type)?.options" :key="name">
|
|
158
|
+
<form-kit v-if="option.type === 'select'" type="l-select" :name="name" :label="name"
|
|
159
|
+
:options="option.options" :validation="option.required ? 'required' : ''"
|
|
160
|
+
:help="option.description"></form-kit>
|
|
161
|
+
|
|
162
|
+
<form-kit v-else-if="option.type === 'boolean'" type="l-toggle" :name="name" :label="name"
|
|
163
|
+
:help="option.description"></form-kit>
|
|
164
|
+
|
|
165
|
+
<form-kit v-else type="l-input" :name="name" :label="name"
|
|
121
166
|
:validation="option.required ? 'required' : ''" :help="option.description"
|
|
122
|
-
:placeholder="option.placeholder"
|
|
167
|
+
:placeholder="option.placeholder" :input-type="option.type === 'password' ? 'password' : 'text'"
|
|
168
|
+
:number="option.type === 'int' ? 'integer' : undefined"></form-kit>
|
|
123
169
|
|
|
124
170
|
</template>
|
|
125
171
|
|
|
126
172
|
</form-kit>
|
|
127
173
|
|
|
174
|
+
<div class="col-12">
|
|
175
|
+
<q-separator class="q-mb-md"></q-separator>
|
|
176
|
+
<div class="text-subtitle2">{{ $t('Filesystem decorators') }}</div>
|
|
177
|
+
<div class="text-caption text-grey-7 q-mb-sm">
|
|
178
|
+
{{ $t('Decorators are applied in order to every file operation.') }}
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<form-kit type="l-repeater" name="decorators" :sortable="true"
|
|
182
|
+
:add-label="$t('Add decorator')" #default="{ value: decorator }">
|
|
183
|
+
<div class="row q-col-gutter-sm">
|
|
184
|
+
<form-kit class="col-12" type="l-select" label="Decorator" name="type"
|
|
185
|
+
validation="required" :options="app.fs.decoratorTypes"
|
|
186
|
+
option-value="name"></form-kit>
|
|
187
|
+
|
|
188
|
+
<form-kit class="col-12" type="group" name="data" v-if="decorator.type">
|
|
189
|
+
<template
|
|
190
|
+
v-for="(option, name) in getDecoratorType(decorator.type)?.options"
|
|
191
|
+
:key="name">
|
|
192
|
+
<form-kit v-if="option.type === 'select'" type="l-select" :name="name"
|
|
193
|
+
:label="name" :options="option.options"
|
|
194
|
+
:validation="option.required ? 'required' : ''"
|
|
195
|
+
:help="option.description"></form-kit>
|
|
196
|
+
|
|
197
|
+
<form-kit v-else-if="option.type === 'boolean'" type="l-toggle" :name="name"
|
|
198
|
+
:label="name" :help="option.description"></form-kit>
|
|
199
|
+
|
|
200
|
+
<form-kit v-else type="l-input" :name="name" :label="name"
|
|
201
|
+
:validation="option.required ? 'required' : ''"
|
|
202
|
+
:help="option.description" :placeholder="option.placeholder"
|
|
203
|
+
:input-type="option.type === 'password' ? 'password' : 'text'"
|
|
204
|
+
:number="option.type === 'int' ? 'integer' : undefined"></form-kit>
|
|
205
|
+
</template>
|
|
206
|
+
</form-kit>
|
|
207
|
+
</div>
|
|
208
|
+
</form-kit>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
128
211
|
</form-kit>
|
|
129
212
|
|
|
130
213
|
</q-card>
|
|
@@ -133,9 +216,7 @@ const getType = (type) => {
|
|
|
133
216
|
<q-table :rows="items" :columns="columns" selection="multiple" flat bordered dense separator="cell"
|
|
134
217
|
v-model:selected="selected" row-key="uuid" :loading="loading">
|
|
135
218
|
<template #top-left>
|
|
136
|
-
<q-btn icon="sym_o_add" @click="
|
|
137
|
-
data: {}
|
|
138
|
-
}; dialog = true" flat round dense>
|
|
219
|
+
<q-btn icon="sym_o_add" @click="openAdd" flat round dense>
|
|
139
220
|
<q-tooltip>Add File System</q-tooltip>
|
|
140
221
|
</q-btn>
|
|
141
222
|
</template>
|
|
@@ -152,16 +233,36 @@ const getType = (type) => {
|
|
|
152
233
|
<q-item v-for="(value, key) in props.row.data" :key="key">
|
|
153
234
|
<q-item-section>
|
|
154
235
|
<q-item-label caption>{{ key }}</q-item-label>
|
|
155
|
-
<q-item-label>{{ value }}</q-item-label>
|
|
236
|
+
<q-item-label>{{ displayValue(key, value) }}</q-item-label>
|
|
237
|
+
</q-item-section>
|
|
238
|
+
</q-item>
|
|
239
|
+
</q-list>
|
|
240
|
+
</q-td>
|
|
241
|
+
</template>
|
|
242
|
+
|
|
243
|
+
<template #body-cell-decorators="props">
|
|
244
|
+
<q-td>
|
|
245
|
+
<q-list v-if="props.row.decorators?.length" flat dense>
|
|
246
|
+
<q-item v-for="(decorator, index) in props.row.decorators"
|
|
247
|
+
:key="`${decorator.type}-${index}`">
|
|
248
|
+
<q-item-section>
|
|
249
|
+
<q-item-label>{{ getDecoratorType(decorator.type)?.label ?? decorator.type }}</q-item-label>
|
|
250
|
+
<q-item-label caption>
|
|
251
|
+
<span v-if="decorator.data?.prefix">
|
|
252
|
+
{{ decorator.data.prefix }} /
|
|
253
|
+
</span>
|
|
254
|
+
{{ decorator.data?.scope ?? 'fixed' }}
|
|
255
|
+
</q-item-label>
|
|
156
256
|
</q-item-section>
|
|
157
257
|
</q-item>
|
|
158
258
|
</q-list>
|
|
259
|
+
<span v-else class="text-grey-6">—</span>
|
|
159
260
|
</q-td>
|
|
160
261
|
</template>
|
|
161
262
|
|
|
162
263
|
<template #body-cell-action="props">
|
|
163
264
|
<q-td auto-width>
|
|
164
|
-
<q-btn icon="sym_o_edit" @click="
|
|
265
|
+
<q-btn icon="sym_o_edit" @click="openEdit(props.row)" flat round dense>
|
|
165
266
|
<q-tooltip>Edit</q-tooltip>
|
|
166
267
|
</q-btn>
|
|
167
268
|
</q-td>
|
|
@@ -1,32 +1,49 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
2
|
+
import { reset } from "@formkit/core";
|
|
3
|
+
import { m, notify, q } from "#imports";
|
|
3
4
|
const { my } = await q({
|
|
4
5
|
my: {
|
|
5
6
|
username: true,
|
|
6
7
|
email: true,
|
|
7
8
|
first_name: true,
|
|
8
|
-
last_name: true
|
|
9
|
+
last_name: true,
|
|
10
|
+
phone: true,
|
|
11
|
+
addr1: true,
|
|
12
|
+
addr2: true,
|
|
13
|
+
addr3: true
|
|
9
14
|
}
|
|
10
15
|
});
|
|
11
16
|
const onSubmit = async (data, form) => {
|
|
12
|
-
|
|
17
|
+
await m("updateMy", {
|
|
13
18
|
data: {
|
|
14
19
|
email: data.email,
|
|
15
20
|
first_name: data.first_name,
|
|
16
|
-
last_name: data.last_name
|
|
21
|
+
last_name: data.last_name,
|
|
22
|
+
phone: data.phone,
|
|
23
|
+
addr1: data.addr1,
|
|
24
|
+
addr2: data.addr2,
|
|
25
|
+
addr3: data.addr3
|
|
17
26
|
}
|
|
18
|
-
}).then(() => {
|
|
19
|
-
notify("Your profile has been updated");
|
|
20
|
-
reset(form, data);
|
|
21
27
|
});
|
|
28
|
+
notify("Your profile has been updated");
|
|
29
|
+
reset(form, data);
|
|
22
30
|
};
|
|
23
31
|
</script>
|
|
24
32
|
|
|
25
33
|
<template>
|
|
26
|
-
<FormKit type="l-form" :value="my" :bordered="false" @submit="onSubmit">
|
|
27
|
-
<
|
|
28
|
-
<FormKit type="l-input" label="
|
|
29
|
-
<FormKit type="l-input" label="
|
|
30
|
-
<FormKit type="l-input" label="
|
|
34
|
+
<FormKit type="l-form" layout="stack" :value="my" :bordered="false" @submit="onSubmit">
|
|
35
|
+
<div class="text-h6">{{ $t("Personal information") }}</div>
|
|
36
|
+
<FormKit type="l-input" label="Username" name="username" readonly />
|
|
37
|
+
<FormKit type="l-input" label="Email" name="email" validation="required|email" />
|
|
38
|
+
<FormKit type="l-input" label="First name" name="first_name" validation="required" />
|
|
39
|
+
<FormKit type="l-input" label="Last name" name="last_name" />
|
|
40
|
+
|
|
41
|
+
<q-separator spaced />
|
|
42
|
+
|
|
43
|
+
<div class="text-h6">{{ $t("Contact information") }}</div>
|
|
44
|
+
<FormKit type="l-input" label="Phone" name="phone" />
|
|
45
|
+
<FormKit type="l-input" label="Address1" name="addr1" />
|
|
46
|
+
<FormKit type="l-input" label="Address2" name="addr2" />
|
|
47
|
+
<FormKit type="l-input" label="Address3" name="addr3" />
|
|
31
48
|
</FormKit>
|
|
32
49
|
</template>
|
|
@@ -1,34 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
my: {
|
|
6
|
-
phone: true,
|
|
7
|
-
addr1: true,
|
|
8
|
-
addr2: true,
|
|
9
|
-
addr3: true
|
|
10
|
-
}
|
|
2
|
+
import { navigateTo } from "#imports";
|
|
3
|
+
await navigateTo("/User/setting", {
|
|
4
|
+
replace: true
|
|
11
5
|
});
|
|
12
|
-
const onSubmit = (data, form) => {
|
|
13
|
-
return m("updateMy", {
|
|
14
|
-
data: {
|
|
15
|
-
phone: data.phone,
|
|
16
|
-
addr1: data.addr1,
|
|
17
|
-
addr2: data.addr2,
|
|
18
|
-
addr3: data.addr3
|
|
19
|
-
}
|
|
20
|
-
}).then(() => {
|
|
21
|
-
notify("Your information has been updated");
|
|
22
|
-
reset(form, data);
|
|
23
|
-
});
|
|
24
|
-
};
|
|
25
6
|
</script>
|
|
26
7
|
|
|
27
8
|
<template>
|
|
28
|
-
<
|
|
29
|
-
<FormKit type="l-input" label="Phone" name="phone"></FormKit>
|
|
30
|
-
<FormKit type="l-input" label="Address1" name="addr1"></FormKit>
|
|
31
|
-
<FormKit type="l-input" label="Address2" name="addr2"></FormKit>
|
|
32
|
-
<FormKit type="l-input" label="Address3" name="addr3"></FormKit>
|
|
33
|
-
</FormKit>
|
|
9
|
+
<div />
|
|
34
10
|
</template>
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
3
|
-
import { useQuasar } from "quasar";
|
|
2
|
+
import { nextTick, onMounted, reactive, ref } from "vue";
|
|
4
3
|
import { q, useLight } from "#imports";
|
|
5
4
|
import { getApiClient } from "@hostlink/light";
|
|
6
|
-
import { fabGoogle, fabFacebook, fabMicrosoft } from "@quasar/extras/fontawesome-
|
|
5
|
+
import { fabGoogle, fabFacebook, fabMicrosoft } from "@quasar/extras/fontawesome-v7";
|
|
6
|
+
import { loadGoogleIdentityServices } from "../../../utils/socialSdk";
|
|
7
7
|
const api = getApiClient();
|
|
8
8
|
const light = useLight();
|
|
9
|
-
const
|
|
10
|
-
let { app, my } = await q({
|
|
9
|
+
const response = await q({
|
|
11
10
|
app: {
|
|
12
11
|
googleClientId: true,
|
|
13
12
|
microsoftClientId: true,
|
|
@@ -20,123 +19,152 @@ let { app, my } = await q({
|
|
|
20
19
|
facebook: true
|
|
21
20
|
}
|
|
22
21
|
});
|
|
23
|
-
const
|
|
24
|
-
my = reactive(my);
|
|
25
|
-
const
|
|
22
|
+
const app = response.app;
|
|
23
|
+
const my = reactive(response.my);
|
|
24
|
+
const providerLoading = reactive({
|
|
25
|
+
google: false,
|
|
26
|
+
microsoft: false,
|
|
27
|
+
facebook: false
|
|
28
|
+
});
|
|
29
|
+
const googleSdkLoading = ref(false);
|
|
30
|
+
const googleSdkError = ref("");
|
|
31
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
32
|
+
const notifyError = (error) => {
|
|
33
|
+
light.notify({
|
|
34
|
+
message: errorMessage(error),
|
|
35
|
+
color: "negative",
|
|
36
|
+
icon: "sym_o_error"
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const refreshAccounts = async () => {
|
|
40
|
+
const result = await q({
|
|
41
|
+
my: {
|
|
42
|
+
google: true,
|
|
43
|
+
microsoft: true,
|
|
44
|
+
facebook: true
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
Object.assign(my, result.my);
|
|
48
|
+
};
|
|
49
|
+
const handleGoogleCredentialResponse = async (credentialResponse) => {
|
|
50
|
+
if (!credentialResponse.credential || providerLoading.google) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
providerLoading.google = true;
|
|
26
54
|
try {
|
|
27
|
-
await api.auth.google.register(
|
|
28
|
-
|
|
29
|
-
my: { google: true }
|
|
30
|
-
});
|
|
31
|
-
my.google = resp.my.google;
|
|
55
|
+
await api.auth.google.register(credentialResponse.credential);
|
|
56
|
+
await refreshAccounts();
|
|
32
57
|
light.notify({
|
|
33
58
|
message: "Google account linked",
|
|
34
59
|
color: "positive"
|
|
35
60
|
});
|
|
36
|
-
} catch (
|
|
37
|
-
|
|
61
|
+
} catch (error) {
|
|
62
|
+
notifyError(error);
|
|
63
|
+
} finally {
|
|
64
|
+
providerLoading.google = false;
|
|
38
65
|
}
|
|
39
66
|
};
|
|
40
|
-
|
|
41
|
-
if (app.googleClientId) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
const initializeGoogle = async () => {
|
|
68
|
+
if (!app.googleClientId || my.google) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
googleSdkLoading.value = true;
|
|
72
|
+
googleSdkError.value = "";
|
|
73
|
+
try {
|
|
74
|
+
const google = await loadGoogleIdentityServices();
|
|
75
|
+
googleSdkLoading.value = false;
|
|
76
|
+
await nextTick();
|
|
77
|
+
const target = document.getElementById("g_id_signin");
|
|
78
|
+
if (!target || my.google) {
|
|
51
79
|
return;
|
|
52
80
|
}
|
|
53
|
-
|
|
54
|
-
nextTick(() => {
|
|
81
|
+
target.replaceChildren();
|
|
55
82
|
google.accounts.id.initialize({
|
|
56
83
|
client_id: app.googleClientId,
|
|
57
84
|
callback: handleGoogleCredentialResponse
|
|
58
85
|
});
|
|
59
|
-
google.accounts.id.renderButton(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
light.dialog({
|
|
74
|
-
title: "Unlink",
|
|
75
|
-
color: "negative",
|
|
76
|
-
message: "Are you sure you want to unlink your Google account?",
|
|
77
|
-
ok: "Yes",
|
|
78
|
-
cancel: "No"
|
|
79
|
-
}).onOk(async () => {
|
|
80
|
-
await api.auth.google.unlink();
|
|
81
|
-
my.gmail = null;
|
|
82
|
-
window.location.reload();
|
|
83
|
-
});
|
|
86
|
+
google.accounts.id.renderButton(target, {
|
|
87
|
+
type: "profile",
|
|
88
|
+
shape: "pill",
|
|
89
|
+
theme: "outline",
|
|
90
|
+
text: "signin_with",
|
|
91
|
+
size: "large",
|
|
92
|
+
logo_alignment: "left"
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
googleSdkError.value = errorMessage(error);
|
|
96
|
+
notifyError(error);
|
|
97
|
+
} finally {
|
|
98
|
+
googleSdkLoading.value = false;
|
|
99
|
+
}
|
|
84
100
|
};
|
|
85
|
-
const
|
|
101
|
+
const unlinkProvider = (provider, label, unlink) => {
|
|
86
102
|
light.dialog({
|
|
87
103
|
title: "Unlink",
|
|
88
104
|
color: "negative",
|
|
89
|
-
message:
|
|
105
|
+
message: `Are you sure you want to unlink your ${label} account?`,
|
|
90
106
|
ok: "Yes",
|
|
91
107
|
cancel: "No"
|
|
92
108
|
}).onOk(async () => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
109
|
+
providerLoading[provider] = true;
|
|
110
|
+
try {
|
|
111
|
+
await unlink();
|
|
112
|
+
await refreshAccounts();
|
|
113
|
+
light.notify({
|
|
114
|
+
message: `${label} account unlinked`,
|
|
115
|
+
color: "positive"
|
|
116
|
+
});
|
|
117
|
+
if (provider === "google") {
|
|
118
|
+
await initializeGoogle();
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
notifyError(error);
|
|
122
|
+
} finally {
|
|
123
|
+
providerLoading[provider] = false;
|
|
124
|
+
}
|
|
96
125
|
});
|
|
97
126
|
};
|
|
98
|
-
const
|
|
127
|
+
const onUnlinkGoogle = () => unlinkProvider("google", "Google", api.auth.google.unlink);
|
|
128
|
+
const onUnlinkMicrosoft = () => unlinkProvider("microsoft", "Microsoft", api.auth.microsoft.unlink);
|
|
129
|
+
const onUnlinkFacebook = () => unlinkProvider("facebook", "Facebook", api.auth.facebook.unlink);
|
|
130
|
+
const onLinkMicrosoft = async (microsoftResponse) => {
|
|
131
|
+
const accountId = microsoftResponse.account?.localAccountId;
|
|
132
|
+
if (!accountId || providerLoading.microsoft) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
providerLoading.microsoft = true;
|
|
99
136
|
try {
|
|
100
|
-
await api.auth.microsoft.register(
|
|
137
|
+
await api.auth.microsoft.register(accountId);
|
|
138
|
+
await refreshAccounts();
|
|
101
139
|
light.notify({
|
|
102
140
|
message: "Microsoft account linked",
|
|
103
141
|
color: "positive"
|
|
104
142
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
color: "negative"
|
|
110
|
-
});
|
|
143
|
+
} catch (error) {
|
|
144
|
+
notifyError(error);
|
|
145
|
+
} finally {
|
|
146
|
+
providerLoading.microsoft = false;
|
|
111
147
|
}
|
|
112
148
|
};
|
|
113
149
|
const onLinkFacebook = async (accessToken) => {
|
|
150
|
+
if (providerLoading.facebook) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
providerLoading.facebook = true;
|
|
114
154
|
try {
|
|
115
155
|
await api.auth.facebook.register(accessToken);
|
|
156
|
+
await refreshAccounts();
|
|
116
157
|
light.notify({
|
|
117
158
|
message: "Facebook account linked",
|
|
118
159
|
color: "positive"
|
|
119
160
|
});
|
|
120
|
-
} catch (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
});
|
|
161
|
+
} catch (error) {
|
|
162
|
+
notifyError(error);
|
|
163
|
+
} finally {
|
|
164
|
+
providerLoading.facebook = false;
|
|
125
165
|
}
|
|
126
166
|
};
|
|
127
|
-
|
|
128
|
-
light.dialog({
|
|
129
|
-
title: "Unlink",
|
|
130
|
-
color: "negative",
|
|
131
|
-
message: "Are you sure you want to unlink your Facebook account?",
|
|
132
|
-
ok: "Yes",
|
|
133
|
-
cancel: "No"
|
|
134
|
-
}).onOk(async () => {
|
|
135
|
-
await api.auth.facebook.unlink();
|
|
136
|
-
my.facebook = null;
|
|
137
|
-
window.location.reload();
|
|
138
|
-
});
|
|
139
|
-
};
|
|
167
|
+
onMounted(initializeGoogle);
|
|
140
168
|
const text1 = "Your social accounts are securely linked using industry-standard OAuth protocols. You can unlink them at any time.";
|
|
141
169
|
</script>
|
|
142
170
|
|
|
@@ -174,14 +202,21 @@ const text1 = "Your social accounts are securely linked using industry-standard
|
|
|
174
202
|
<div class="text-caption text-grey-6">{{ $t('Connected Account') }}</div>
|
|
175
203
|
<div class="text-body2 text-weight-medium q-mt-xs">{{ my.google }}</div>
|
|
176
204
|
</div>
|
|
177
|
-
<q-btn class="full-width" color="negative" outline @click="
|
|
178
|
-
:label="$t('Unlink Account')"
|
|
205
|
+
<q-btn class="full-width" color="negative" outline @click="onUnlinkGoogle"
|
|
206
|
+
icon="sym_o_link_off" :label="$t('Unlink Account')"
|
|
207
|
+
:loading="providerLoading.google" />
|
|
179
208
|
</template>
|
|
180
209
|
<template v-else>
|
|
181
210
|
<div class="text-center q-mb-md text-grey-6">
|
|
182
211
|
{{ $t('Click to link your Google account for quick sign-in') }}
|
|
183
212
|
</div>
|
|
184
|
-
<div
|
|
213
|
+
<div v-if="googleSdkLoading" class="flex justify-center q-pa-sm">
|
|
214
|
+
<q-spinner color="primary" size="32px" />
|
|
215
|
+
</div>
|
|
216
|
+
<q-banner v-else-if="googleSdkError" class="bg-red-1 text-negative">
|
|
217
|
+
{{ $t('Google login is not available') }}
|
|
218
|
+
</q-banner>
|
|
219
|
+
<div v-else id="g_id_signin" class="flex justify-center"></div>
|
|
185
220
|
</template>
|
|
186
221
|
</q-card-section>
|
|
187
222
|
<q-card-section v-else>
|
|
@@ -219,13 +254,17 @@ const text1 = "Your social accounts are securely linked using industry-standard
|
|
|
219
254
|
<div class="text-body2 text-weight-medium q-mt-xs">{{ my.microsoft }}</div>
|
|
220
255
|
</div>
|
|
221
256
|
<q-btn class="full-width" color="negative" outline @click="onUnlinkMicrosoft"
|
|
222
|
-
icon="sym_o_link_off" :label="$t('Unlink Account')"
|
|
257
|
+
icon="sym_o_link_off" :label="$t('Unlink Account')"
|
|
258
|
+
:loading="providerLoading.microsoft" />
|
|
223
259
|
</template>
|
|
224
260
|
<template v-else>
|
|
225
261
|
<div class="text-center q-mb-md text-grey-6">
|
|
226
262
|
{{ $t('Click to link your Microsoft account for quick sign-in') }}
|
|
227
263
|
</div>
|
|
228
|
-
<div class="flex justify-center">
|
|
264
|
+
<div v-if="providerLoading.microsoft" class="flex justify-center q-pa-sm">
|
|
265
|
+
<q-spinner color="primary" size="32px" />
|
|
266
|
+
</div>
|
|
267
|
+
<div v-else class="flex justify-center">
|
|
229
268
|
<l-microsoft-button :client-id="app.microsoftClientId"
|
|
230
269
|
:tenant-id="app.microsoftTenantId" @login="onLinkMicrosoft" />
|
|
231
270
|
</div>
|
|
@@ -266,14 +305,16 @@ const text1 = "Your social accounts are securely linked using industry-standard
|
|
|
266
305
|
<div class="text-body2 text-weight-medium q-mt-xs">{{ my.facebook }}</div>
|
|
267
306
|
</div>
|
|
268
307
|
<q-btn class="full-width" color="negative" outline @click="onUnlinkFacebook"
|
|
269
|
-
icon="sym_o_link_off" :label="$t('Unlink Account')"
|
|
308
|
+
icon="sym_o_link_off" :label="$t('Unlink Account')"
|
|
309
|
+
:loading="providerLoading.facebook" />
|
|
270
310
|
</template>
|
|
271
311
|
<template v-else>
|
|
272
312
|
<div class="text-center q-mb-md text-grey-6">
|
|
273
313
|
{{ $t('Click to link your Facebook account for quick sign-in') }}
|
|
274
314
|
</div>
|
|
275
315
|
<div class="flex justify-center">
|
|
276
|
-
<l-facebook-button
|
|
316
|
+
<l-facebook-button :app-id="app.facebookAppId"
|
|
317
|
+
:disable="providerLoading.facebook" @login="onLinkFacebook" />
|
|
277
318
|
</div>
|
|
278
319
|
</template>
|
|
279
320
|
</q-card-section>
|