@hostlink/nuxt-light 1.73.1 → 1.74.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.
Files changed (56) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +3 -1
  3. package/dist/runtime/components/L/App.vue +0 -19
  4. package/dist/runtime/components/L/AppMain.d.vue.ts +3 -3
  5. package/dist/runtime/components/L/AppMain.vue +43 -10
  6. package/dist/runtime/components/L/AppMain.vue.d.ts +3 -3
  7. package/dist/runtime/components/L/FacebookButton.d.vue.ts +19 -3
  8. package/dist/runtime/components/L/FacebookButton.vue +73 -12
  9. package/dist/runtime/components/L/FacebookButton.vue.d.ts +19 -3
  10. package/dist/runtime/components/L/FavMenu.d.vue.ts +2 -0
  11. package/dist/runtime/components/L/FavMenu.vue +10 -2
  12. package/dist/runtime/components/L/FavMenu.vue.d.ts +2 -0
  13. package/dist/runtime/components/L/FileUpload.vue +19 -8
  14. package/dist/runtime/components/L/Form.d.vue.ts +29 -56
  15. package/dist/runtime/components/L/Form.vue +70 -80
  16. package/dist/runtime/components/L/Form.vue.d.ts +29 -56
  17. package/dist/runtime/components/L/FormLayout.d.vue.ts +36 -0
  18. package/dist/runtime/components/L/FormLayout.vue +36 -0
  19. package/dist/runtime/components/L/FormLayout.vue.d.ts +36 -0
  20. package/dist/runtime/components/L/GroupSelect.vue +1 -1
  21. package/dist/runtime/components/L/Login.vue +1 -1
  22. package/dist/runtime/components/L/Menu.d.vue.ts +2 -0
  23. package/dist/runtime/components/L/Menu.vue +4 -4
  24. package/dist/runtime/components/L/Menu.vue.d.ts +2 -0
  25. package/dist/runtime/components/L/System/Setting/security.d.vue.ts +2 -0
  26. package/dist/runtime/components/L/System/Setting/security.vue +7 -1
  27. package/dist/runtime/components/L/System/Setting/security.vue.d.ts +2 -0
  28. package/dist/runtime/composables/useLight.d.ts +0 -4
  29. package/dist/runtime/formkit/Form.d.vue.ts +2 -2
  30. package/dist/runtime/formkit/Form.vue +15 -18
  31. package/dist/runtime/formkit/Form.vue.d.ts +2 -2
  32. package/dist/runtime/formkit/index.js +2 -0
  33. package/dist/runtime/locales/en.json +8 -2
  34. package/dist/runtime/locales/zh-hk.json +8 -2
  35. package/dist/runtime/pages/Role/[name]/update-child.vue +4 -1
  36. package/dist/runtime/pages/System/fs.vue +116 -15
  37. package/dist/runtime/pages/User/setting/index.vue +29 -12
  38. package/dist/runtime/pages/User/setting/information.vue +4 -28
  39. package/dist/runtime/pages/User/setting/open_id.vue +132 -91
  40. package/dist/runtime/pages/User/setting/two-factor-auth.vue +388 -143
  41. package/dist/runtime/pages/User/setting.vue +3 -4
  42. package/dist/runtime/types/form.d.ts +2 -0
  43. package/dist/runtime/types/form.js +0 -0
  44. package/dist/runtime/utils/filterMenuItems.d.ts +10 -0
  45. package/dist/runtime/utils/filterMenuItems.js +34 -0
  46. package/dist/runtime/utils/formLayout.d.ts +2 -0
  47. package/dist/runtime/utils/formLayout.js +7 -0
  48. package/dist/runtime/utils/socialSdk.d.ts +2 -0
  49. package/dist/runtime/utils/socialSdk.js +127 -0
  50. package/package.json +14 -14
  51. package/dist/runtime/pages/Role/add2.d.vue.ts +0 -3
  52. package/dist/runtime/pages/Role/add2.vue +0 -57
  53. package/dist/runtime/pages/Role/add2.vue.d.ts +0 -3
  54. package/dist/runtime/pages/System/test.d.vue.ts +0 -3
  55. package/dist/runtime/pages/System/test.vue +0 -13
  56. 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
- list: true,
11
- types: true
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.fs.list);
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 (selected2) => {
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 selected2) {
66
+ for (let row of rows) {
50
67
  await m("lightFSdelete", {
51
68
  uuid: row.uuid
52
69
  });
53
70
  }
54
- selected2.value = [];
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: 500px;">
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).options">
120
- <form-kit type="l-input" :name="name" :label="name"
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"></form-kit>
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="value = {
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="value = props.row; dialog = true" flat round dense>
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 { q, m, notify } from "#imports";
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
- return await m("updateMy", {
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
- <FormKit type="l-input" label="Username" name="username" readonly></FormKit>
28
- <FormKit type="l-input" label="Email" name="email" validation="required|email"></FormKit>
29
- <FormKit type="l-input" label="First name" name="first_name" validation="required"></FormKit>
30
- <FormKit type="l-input" label="Last name" name="last_name"></FormKit>
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 { reset } from "@formkit/core";
3
- import { q, m, notify } from "#imports";
4
- const { my } = await q({
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
- <FormKit type="l-form" :value="my" :bordered="false" @submit="onSubmit">
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 { reactive, onMounted, nextTick } from "vue";
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-v6";
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 quasar = useQuasar();
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 $q = useQuasar();
24
- my = reactive(my);
25
- const handleGoogleCredentialResponse = async (response) => {
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(response.credential);
28
- const resp = await q({
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 (e) {
37
- console.log(e);
61
+ } catch (error) {
62
+ notifyError(error);
63
+ } finally {
64
+ providerLoading.google = false;
38
65
  }
39
66
  };
40
- onMounted(() => {
41
- if (app.googleClientId) {
42
- if (!window.google) {
43
- quasar.notify({
44
- message: "Google login is not available",
45
- //set script https://accounts.google.com/gsi/client in nuuxt.config.js
46
- color: "negative",
47
- icon: "sym_o_error",
48
- position: "top",
49
- timeout: 2e3
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
- document.getElementById("g_id_signin"),
61
- {
62
- type: "profile",
63
- shape: "pill",
64
- theme: "outline",
65
- text: "signin_with",
66
- size: "large",
67
- logo_alignment: "left"
68
- }
69
- );
70
- });
71
- });
72
- const onUnlink = async () => {
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 onUnlinkMicrosoft = async () => {
101
+ const unlinkProvider = (provider, label, unlink) => {
86
102
  light.dialog({
87
103
  title: "Unlink",
88
104
  color: "negative",
89
- message: "Are you sure you want to unlink your Microsoft account?",
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
- await api.auth.microsoft.unlink();
94
- my.microsoft = null;
95
- window.location.reload();
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 onLinkMicrosoft = async (resp) => {
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(resp.account.localAccountId);
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
- window.location.reload();
106
- } catch (e) {
107
- light.notify({
108
- message: e.message,
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 (e) {
121
- light.notify({
122
- message: e.message,
123
- color: "negative"
124
- });
161
+ } catch (error) {
162
+ notifyError(error);
163
+ } finally {
164
+ providerLoading.facebook = false;
125
165
  }
126
166
  };
127
- const onUnlinkFacebook = async () => {
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="onUnlink" icon="sym_o_link_off"
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 id="g_id_signin" class="flex justify-center"></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 @login="onLinkFacebook" />
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>