@live-change/user-frontend 0.8.64 → 0.8.66

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.
@@ -2,29 +2,29 @@
2
2
  "version": "6.0",
3
3
  "nxVersion": "18.0.6",
4
4
  "deps": {
5
- "@live-change/cli": "0.8.33",
6
- "@live-change/dao": "0.8.33",
7
- "@live-change/dao-vue3": "0.8.33",
8
- "@live-change/dao-websocket": "0.8.33",
9
- "@live-change/email-service": "0.8.33",
10
- "@live-change/framework": "0.8.33",
11
- "@live-change/identicon-service": "0.8.33",
12
- "@live-change/image-frontend": "0.8.33",
13
- "@live-change/message-authentication-service": "0.8.33",
14
- "@live-change/notification-service": "0.8.33",
15
- "@live-change/password-authentication-service": "0.8.33",
16
- "@live-change/pattern": "0.8.33",
17
- "@live-change/secret-code-service": "0.8.33",
18
- "@live-change/secret-link-service": "0.8.33",
19
- "@live-change/security-frontend": "0.8.33",
20
- "@live-change/security-service": "0.8.33",
21
- "@live-change/session-service": "0.8.33",
22
- "@live-change/timer-service": "0.8.33",
23
- "@live-change/upload-service": "0.8.33",
24
- "@live-change/user-identification-service": "0.8.33",
25
- "@live-change/user-service": "0.8.33",
26
- "@live-change/vue3-components": "0.8.33",
27
- "@live-change/vue3-ssr": "0.8.33",
5
+ "@live-change/cli": "0.8.64",
6
+ "@live-change/dao": "0.8.64",
7
+ "@live-change/dao-vue3": "0.8.64",
8
+ "@live-change/dao-websocket": "0.8.64",
9
+ "@live-change/email-service": "0.8.64",
10
+ "@live-change/framework": "0.8.64",
11
+ "@live-change/identicon-service": "0.8.64",
12
+ "@live-change/image-frontend": "0.8.64",
13
+ "@live-change/message-authentication-service": "0.8.64",
14
+ "@live-change/notification-service": "0.8.64",
15
+ "@live-change/password-authentication-service": "0.8.64",
16
+ "@live-change/pattern": "0.8.64",
17
+ "@live-change/secret-code-service": "0.8.64",
18
+ "@live-change/secret-link-service": "0.8.64",
19
+ "@live-change/security-frontend": "0.8.64",
20
+ "@live-change/security-service": "0.8.64",
21
+ "@live-change/session-service": "0.8.64",
22
+ "@live-change/timer-service": "0.8.64",
23
+ "@live-change/upload-service": "0.8.64",
24
+ "@live-change/user-identification-service": "0.8.64",
25
+ "@live-change/user-service": "0.8.64",
26
+ "@live-change/vue3-components": "0.8.64",
27
+ "@live-change/vue3-ssr": "0.8.64",
28
28
  "@vueuse/core": "^10.11.0",
29
29
  "codeceptjs-assert": "^0.0.5",
30
30
  "codeceptjs-video-helper": "0.1.3",
@@ -43,13 +43,13 @@
43
43
  "vue-router": "^4.3.3",
44
44
  "vue3-scroll-border": "0.1.6",
45
45
  "wtfnode": "^0.9.1",
46
- "@live-change/codeceptjs-helper": "0.8.33",
47
- "codeceptjs": "^3.5.12",
46
+ "@live-change/codeceptjs-helper": "0.8.64",
47
+ "codeceptjs": "^3.6.5",
48
48
  "generate-password": "1.7.1",
49
49
  "playwright": "^1.41.2",
50
50
  "random-profile-generator": "^2.3.0",
51
51
  "txtgen": "^3.0.6",
52
- "webdriverio": "^8.31.1"
52
+ "webdriverio": "^8.40.2"
53
53
  },
54
54
  "pathMappings": {},
55
55
  "nxJsonPlugins": [],
@@ -60,9 +60,13 @@
60
60
  "file": ".gitignore",
61
61
  "hash": "16223102139109646162"
62
62
  },
63
+ {
64
+ "file": "LICENSE",
65
+ "hash": "10221119794387200971"
66
+ },
63
67
  {
64
68
  "file": "e2e/codecept.conf.js",
65
- "hash": "928331161337695761"
69
+ "hash": "13791327886840311692"
66
70
  },
67
71
  {
68
72
  "file": "e2e/connectEmailCode.test.js",
@@ -178,11 +182,15 @@
178
182
  },
179
183
  {
180
184
  "file": "front/src/connected/Connected.vue",
181
- "hash": "9003743983857262137"
185
+ "hash": "11506706671292138762"
186
+ },
187
+ {
188
+ "file": "front/src/connected/connected.js",
189
+ "hash": "17119205448655692635"
182
190
  },
183
191
  {
184
192
  "file": "front/src/connected/routes.js",
185
- "hash": "3520804499341680062"
193
+ "hash": "2551528109489035833"
186
194
  },
187
195
  {
188
196
  "file": "front/src/delete/Delete.vue",
@@ -208,6 +216,18 @@
208
216
  "file": "front/src/entry-server.js",
209
217
  "hash": "3852150522654880349"
210
218
  },
219
+ {
220
+ "file": "front/src/google-access/GoogleAccess.vue",
221
+ "hash": "9529831848748475095"
222
+ },
223
+ {
224
+ "file": "front/src/google-access/GoogleAccessGained.vue",
225
+ "hash": "4309063406044103630"
226
+ },
227
+ {
228
+ "file": "front/src/google-access/routes.js",
229
+ "hash": "6244550399322220890"
230
+ },
211
231
  {
212
232
  "file": "front/src/identification/IdentificationSettings.vue",
213
233
  "hash": "15002414951607988030"
@@ -218,7 +238,7 @@
218
238
  },
219
239
  {
220
240
  "file": "front/src/identification/UserIdentification.vue",
221
- "hash": "315380535873476393"
241
+ "hash": "12949250406055003638"
222
242
  },
223
243
  {
224
244
  "file": "front/src/identification/routes.js",
@@ -278,7 +298,7 @@
278
298
  },
279
299
  {
280
300
  "file": "front/src/nav/UserIcon.vue",
281
- "hash": "1814392438544680556"
301
+ "hash": "3324270693059299280"
282
302
  },
283
303
  {
284
304
  "file": "front/src/nav/UserMenu.vue",
@@ -354,7 +374,7 @@
354
374
  },
355
375
  {
356
376
  "file": "front/src/router.js",
357
- "hash": "4992800822957455657"
377
+ "hash": "6232756401755977821"
358
378
  },
359
379
  {
360
380
  "file": "front/src/settings/Settings.vue",
@@ -374,19 +394,27 @@
374
394
  },
375
395
  {
376
396
  "file": "front/src/sign/GoogleAuth.vue",
377
- "hash": "5043472338996276712"
397
+ "hash": "17904987660087619857"
378
398
  },
379
399
  {
380
400
  "file": "front/src/sign/GoogleAuthReturn.vue",
381
- "hash": "11578892278379018497"
401
+ "hash": "11166765015834314072"
402
+ },
403
+ {
404
+ "file": "front/src/sign/LinkedinAuth.vue",
405
+ "hash": "9567465782490495596"
406
+ },
407
+ {
408
+ "file": "front/src/sign/LinkedinAuthReturn.vue",
409
+ "hash": "9428875039032707697"
382
410
  },
383
411
  {
384
412
  "file": "front/src/sign/SignInEmail.vue",
385
- "hash": "1815095713448644951"
413
+ "hash": "15480628897033099010"
386
414
  },
387
415
  {
388
416
  "file": "front/src/sign/SignInFinished.vue",
389
- "hash": "844919637394202510"
417
+ "hash": "11444495191715209127"
390
418
  },
391
419
  {
392
420
  "file": "front/src/sign/SignOut.vue",
@@ -402,11 +430,11 @@
402
430
  },
403
431
  {
404
432
  "file": "front/src/sign/SignUpFinished.vue",
405
- "hash": "15083286059095236347"
433
+ "hash": "4534581805740174218"
406
434
  },
407
435
  {
408
436
  "file": "front/src/sign/routes.js",
409
- "hash": "731553876160166852"
437
+ "hash": "1023097621604756838"
410
438
  },
411
439
  {
412
440
  "file": "front/src/utils/countries.js",
@@ -416,21 +444,25 @@
416
444
  "file": "front/src/utils/googleAuth.js",
417
445
  "hash": "10682927559112869908"
418
446
  },
447
+ {
448
+ "file": "front/src/utils/linkedinAuth.js",
449
+ "hash": "16435105867959168771"
450
+ },
419
451
  {
420
452
  "file": "front/vite.config.js",
421
453
  "hash": "4207480426880236517"
422
454
  },
423
455
  {
424
456
  "file": "index.js",
425
- "hash": "6259321642807355421"
457
+ "hash": "16467025442967385199"
426
458
  },
427
459
  {
428
460
  "file": "package.json",
429
- "hash": "12911801514831005291"
461
+ "hash": "4875568356135693252"
430
462
  },
431
463
  {
432
464
  "file": "server/app.config.js",
433
- "hash": "18401554844996247569"
465
+ "hash": "14052619601583710778"
434
466
  },
435
467
  {
436
468
  "file": "server/init-functions.js",
@@ -446,7 +478,7 @@
446
478
  },
447
479
  {
448
480
  "file": "server/services.list.js",
449
- "hash": "16537572580698486843"
481
+ "hash": "2018796374883415113"
450
482
  },
451
483
  {
452
484
  "file": "server/start.js",
Binary file
@@ -25,7 +25,7 @@
25
25
  </Divider>
26
26
 
27
27
  <router-link :to="{ name: 'user:connect-phone' }">
28
- <Button label="Add Phone" icon="pi pi-github" class="w-full p-button-secondary mb-2" />
28
+ <Button label="Add Phone" icon="pi pi-mobile" class="w-full p-button-secondary mb-2" />
29
29
  </router-link>
30
30
 
31
31
  <!-- <Button label="Connect GitHub account" icon="pi pi-github" class="w-full p-button-secondary mb-2"></Button>
@@ -12,10 +12,10 @@
12
12
  <label for="email" class="block text-900 font-medium mb-2">
13
13
  Phone number
14
14
  </label>
15
- <InputText id="email" type="text" class="w-full"
16
- aria-describedby="email-help" :class="{ 'p-invalid': data.phoneError}"
15
+ <PhoneInput id="phone" class="w-full"
16
+ aria-describedby="phone-help" :class="{ 'p-invalid': data.phoneError }"
17
17
  v-model="data.phone" />
18
- <small v-if="data.phoneError" id="email-help" class="p-error">{{ data.phoneError }}</small>
18
+ <small v-if="data.phoneError" id="phone-help" class="p-error">{{ data.phoneError }}</small>
19
19
  </div>
20
20
 
21
21
  <Button label="Add Phone" icon="pi pi-mobile" class="w-full" type="submit" />
@@ -25,7 +25,7 @@
25
25
  </Divider>
26
26
 
27
27
  <router-link :to="{ name: 'user:connect-email' }">
28
- <Button label="Add Email" icon="pi pi-github" class="w-full p-button-secondary mb-2" />
28
+ <Button label="Add Email" icon="pi pi-envelope" class="w-full p-button-secondary mb-2" />
29
29
  </router-link>
30
30
 
31
31
  <!-- <Button label="Connect GitHub account" icon="pi pi-github" class="w-full p-button-secondary mb-2"></Button>-->
@@ -41,6 +41,7 @@
41
41
  import Checkbox from "primevue/checkbox"
42
42
  import Button from "primevue/button"
43
43
  import Divider from "primevue/divider"
44
+ import PhoneInput from "../phone/PhoneInput.vue"
44
45
 
45
46
  import { useRouter } from 'vue-router'
46
47
  const router = useRouter()
@@ -27,6 +27,11 @@
27
27
  <i class="pi pi-google mr-2"></i>
28
28
  <span class="block text-900 font-medium text-lg">{{ account.email }}</span>
29
29
  </div>
30
+ <div v-if="account.accountType.accountType === 'linkedin'"
31
+ class="flex flex-row align-items-center">
32
+ <i class="pi pi-linkedin mr-2"></i>
33
+ <span class="block text-900 font-medium text-lg">{{ account.name }}</span>
34
+ </div>
30
35
  <pre v-else>{{ account }}</pre>
31
36
  <Button class="p-button-text p-button-plain p-button-rounded mr-1" icon="pi pi-times"
32
37
  v-if="canDelete"
@@ -15,6 +15,9 @@ export function routes(config = {}) {
15
15
  route({ name: 'user:connect-google', path: prefix + 'connect-google',
16
16
  redirect: { name: 'user:googleAuth', params: { action: 'connectGoogle' } } }),
17
17
 
18
+ route({ name: 'user:connect-linkedin', path: prefix + 'connect-linkedin',
19
+ redirect: { name: 'user:linkedinAuth', params: { action: 'connectLinkedin' } } }),
20
+
18
21
  route({ name: 'user:connectFinished', path: prefix + 'connect-finished',
19
22
  component: () => import("./ConnectFinished.vue") }),
20
23
 
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <div class="flex flex-row align-items-center">
3
+ <AutoComplete v-model="selectedCountry" dropdown optionLabel="dial_code" placeholder="+XX" forceSelection
4
+ :suggestions="filteredCountries" @complete="searchCountry"
5
+ class="mr-2 w-14rem">
6
+ <template #option="slotProps">
7
+ <div class="flex align-items-center">
8
+ <img :alt="slotProps.option.name"
9
+ src="../../public/images/flag_placeholder.png"
10
+ :class="`flag flag-${slotProps.option.code.toLowerCase()} mr-2`"
11
+ style="width: 18px; height: 12.27px" />
12
+ <div>{{ slotProps.option.name }}</div>
13
+ </div>
14
+ </template>}}
15
+ </AutoComplete>
16
+ <InputText v-model="rest" :disabled="!selectedCountry" class="w-full"
17
+ pattern="[0-9 ]*" inputmode="numeric" ref="phoneInput"
18
+ @keyup="e => e.target.value = e.target.value.replace(/[^0-9 ]/g, '')"/>
19
+ </div>
20
+ </template>
21
+
22
+ <script setup>
23
+
24
+ import AutoComplete from 'primevue/autocomplete'
25
+ import InputText from 'primevue/inputtext'
26
+
27
+ import countries from '../utils/countries.js'
28
+
29
+ import { defineProps, defineModel, toRefs, ref, computed, watch } from 'vue'
30
+
31
+ const phoneInput = ref()
32
+
33
+ const value = defineModel({
34
+ type: String,
35
+ required: true
36
+ })
37
+
38
+ const props = defineProps({
39
+ id: {
40
+ type: String,
41
+ required: true
42
+ },
43
+ })
44
+
45
+ const selectedCountry = ref()
46
+ watch(value, (newValue) => {
47
+ if(!newValue) return
48
+ const found = countries.find((country) => newValue.startsWith(country.dial_code))
49
+ if(found) {
50
+ selectedCountry.value = found
51
+ if(phoneInput.value?.$el) {
52
+ const el = phoneInput.value.$el
53
+ //console.log("PHONE INPUT", el)
54
+ setTimeout(() => {
55
+ el.focus()
56
+ }, 100)
57
+ }
58
+ }
59
+ })
60
+ watch(selectedCountry, (country) => {
61
+ if(!country) return
62
+ const currentCountry = value.value && countries.find((country) => value.value.startsWith(country.dial_code))
63
+ const code = typeof country === 'object' ? country.dial_code : country.replace(/[^\d]/g, '')
64
+ if(currentCountry) {
65
+ value.value = value.value.replace(currentCountry.dial_code, code)
66
+ } else {
67
+ value.value = code
68
+ }
69
+ })
70
+
71
+ const rest = computed({
72
+ get: () => value.value && (value.value
73
+ .replace(selectedCountry.value?.dial_code, '')
74
+ .replace(/[^\d ]/g, '')),
75
+ set: (rest) => value.value = selectedCountry.value?.dial_code + rest.replace(/[^\d ]/g, '')
76
+ })
77
+
78
+ const filteredCountries = ref(countries)
79
+
80
+ function searchCountry(event) {
81
+ const numbers = event.query.replace(/[^\d]/g, '')
82
+ console.log("Search country", event.query, numbers, selectedCountry.value)
83
+ if(selectedCountry.value !== null && typeof selectedCountry.value === 'object') selectedCountry.value = null
84
+ filteredCountries.value = countries.filter((country) =>
85
+ country.name.toLowerCase().startsWith(event.query.toLowerCase())
86
+ || country.code.toLowerCase().startsWith(event.query.toLowerCase())
87
+ || (numbers.length > 0 && country.dial_code.replace(/[^\d]/g, '').startsWith(numbers))
88
+ )
89
+ console.log("Found", filteredCountries.value)
90
+ }
91
+
92
+ import { usePath, live } from '@live-change/vue3-ssr'
93
+ const path = usePath()
94
+ const geoIpPath = path.geoIp.myCountry({})
95
+
96
+ const myCountry = await live(geoIpPath)
97
+
98
+ if(selectedCountry.value == null) {
99
+ selectedCountry.value = countries.find(c => c.code.toLowerCase() === myCountry.value.toLowerCase())
100
+ }
101
+
102
+ </script>
103
+
104
+
105
+ <style scoped lang="scss">
106
+ @import "../utils/flags.scss";
107
+ </style>
@@ -0,0 +1,96 @@
1
+ <template>
2
+ <div class="w-full lg:w-6 md:w-9" v-shared-element:form="{ duration: '300ms', includeChildren: true }">
3
+ <div class="surface-card p-4 shadow-2 border-round">
4
+
5
+ <div class="text-center mb-5">
6
+ <div class="text-900 text-3xl font-medium mb-3">Linkedin authentication</div>
7
+ </div>
8
+
9
+ <div v-if="state === 'canceled'" class="text-center">
10
+ <div class="mb-1">Authentication canceled by user</div>
11
+ <div class="flex flex-row">
12
+ <Button @click="back" label="Go back" icon="pi pi-arrow-left" class="w-full p-button-secondary mb-1" />
13
+ <Button @click="linkedinAuth" label="Try again" icon="pi pi-linkedin" class="w-full p-button-secondary mb-1" />
14
+ </div>
15
+ </div>
16
+ <div v-else-if="state === 'waiting'" class="text-center">
17
+ Authentication will open in this window.
18
+ </div>
19
+ <div v-else-if="state === 'working'" class="text-center">
20
+ Waiting for server...
21
+ </div>
22
+ <div v-else-if="state === 'error'" class="text-center">
23
+ <div>Error during authentication:</div>
24
+ <div>{{ error }}</div>
25
+ </div>
26
+ <div v-else>
27
+ Unknown authentication state: {{ state }}
28
+ </div>
29
+
30
+ </div>
31
+ </div>
32
+ </template>
33
+
34
+ <script setup>
35
+ import { defineProps, toRefs, ref, onMounted, inject } from 'vue'
36
+
37
+ import { useRouter } from 'vue-router'
38
+ const router = useRouter()
39
+
40
+ const workingZone = inject('workingZone')
41
+
42
+ import { linkedinAuthRedirect } from '../utils/linkedinAuth.js'
43
+
44
+ const props = defineProps({
45
+ action: {
46
+ type: String,
47
+ default: 'signInOrSignUp'
48
+ },
49
+ accessType: {
50
+ type: String,
51
+ default: 'offline', //'online'
52
+ },
53
+ scopes: {
54
+ type: Array,
55
+ default: () => ['profile', 'email', 'openid']
56
+ }
57
+ })
58
+
59
+ const { action, accessType, scopes } = toRefs(props)
60
+ const state = ref('waiting')
61
+ const error = ref(null)
62
+
63
+ function linkedinAuth() {
64
+ state.value = 'waiting'
65
+
66
+ workingZone.addPromise('linkedin auth', new Promise((resolve, reject) => {
67
+ setTimeout(() => {
68
+ state.value = 'error'
69
+ error.value = 'redirect_timeout'
70
+ // return reject('redirect timeout?!')
71
+ return resolve()
72
+ }, 4000)
73
+ }))
74
+
75
+ linkedinAuthRedirect({
76
+ scope: (scopes.value ?? []).join(' '),
77
+ redirectUri: document.location.protocol + '//' + document.location.host
78
+ + router.resolve({ name: 'user:linkedinAuthReturn', params: { action: action.value } }).href,
79
+ accessType: accessType.value
80
+ })
81
+
82
+ }
83
+
84
+ async function back() {
85
+ router.go(-1)
86
+ }
87
+
88
+ onMounted(() => {
89
+ linkedinAuth()
90
+ })
91
+
92
+ </script>
93
+
94
+ <style scoped>
95
+
96
+ </style>
@@ -0,0 +1,105 @@
1
+ <template>
2
+ <div class="w-full lg:w-6 md:w-9" v-shared-element:form="{ duration: '300ms', includeChildren: true }">
3
+ <div class="surface-card p-4 shadow-2 border-round">
4
+
5
+ <div class="text-center mb-5">
6
+ <div class="text-900 text-3xl font-medium mb-3">Linkedin authentication</div>
7
+ </div>
8
+
9
+ <div v-if="state === 'canceled'" class="text-center">
10
+ <div class="mb-2 text-red-500">Authentication canceled by user</div>
11
+ <div class="flex flex-row">
12
+ <Button @click="back" label="Go back" icon="pi pi-arrow-left"
13
+ class="w-full p-button-secondary mb-1" />
14
+ </div>
15
+ </div>
16
+ <div v-else-if="state === 'working'" class="text-center">
17
+ Waiting for server...
18
+ </div>
19
+ <div v-else-if="state === 'error'" class="text-center">
20
+ <div>Error during authentication</div>
21
+ <div>{{ error }}</div>
22
+ </div>
23
+ <div v-else>
24
+ Unknown authentication state: {{ state }}
25
+ </div>
26
+
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+ <script setup>
32
+ import { defineProps, toRefs, ref, onMounted, inject } from 'vue'
33
+
34
+ import { useApi } from "@live-change/vue3-ssr"
35
+ const api = useApi()
36
+
37
+ import { useToast } from 'primevue/usetoast'
38
+ const toast = useToast()
39
+
40
+ const workingZone = inject('workingZone')
41
+
42
+ import { useRouter, useRoute } from 'vue-router'
43
+ const router = useRouter()
44
+ const route = useRoute()
45
+
46
+ const props = defineProps({
47
+ action: {
48
+ type: String,
49
+ default: 'signInOrSignUp'
50
+ }
51
+ })
52
+
53
+ const { action } = toRefs(props)
54
+ const state = ref('waiting')
55
+ const error = ref(null)
56
+
57
+ onMounted(async () => {
58
+ const query = route.query
59
+ console.log("QUERY", query)
60
+
61
+ if(!query.code) {
62
+ state.value = 'canceled'
63
+ return
64
+ }
65
+ try {
66
+ const result = await workingZone.addPromise(`linkedin ${action.value}`,
67
+ api.command(['linkedinAuthentication', action.value], {
68
+ redirectUri: document.location.protocol + '//' + document.location.host
69
+ + router.resolve({ name: 'user:linkedinAuthReturn', params: { action: action.value } }).href,
70
+ ...query
71
+ })
72
+ )
73
+ console.log("LINKEDIN AUTH RESULT", result)
74
+ const { action: actionDone, user } = result
75
+ while(user && api.client.value.user !== user) {
76
+ await new Promise(resolve => setTimeout(resolve, 100))
77
+ }
78
+ if(actionDone === 'signIn') {
79
+ router.push({ name: 'user:signInFinished' })
80
+ } else if(actionDone === 'signUp') {
81
+ router.push({ name: 'user:signUpFinished' })
82
+ } else if(actionDone === 'connectLinkedin') {
83
+ router.push({ name: 'user:connected' })
84
+ } else if(actionDone === 'addOfflineAccessToken') {
85
+ router.push({ name: 'user:linkedin-access-gained' })
86
+ } else {
87
+ console.error("Unknown action", actionDone)
88
+ }
89
+ } catch(error) {
90
+ console.error("Linkedin auth error", error)
91
+ toast.add({ severity: 'error', summary: 'Error', detail: 'Error during linkedin authentication', life: 3000 })
92
+ state.value = 'error'
93
+ error.value = error
94
+ }
95
+ })
96
+
97
+ async function back() {
98
+ router.go(-1)
99
+ }
100
+
101
+ </script>
102
+
103
+ <style scoped>
104
+
105
+ </style>
@@ -59,6 +59,14 @@
59
59
  />
60
60
  </router-link>
61
61
 
62
+ <router-link :to="{ name: 'user:linkedinAuth', params: { action: 'signInOrSignUp' } }" class="no-underline">
63
+ <Button
64
+ label="Sign In with Linkedin"
65
+ icon="pi pi-linkedin"
66
+ class="w-full p-button-secondary mb-1"
67
+ />
68
+ </router-link>
69
+
62
70
  </div>
63
71
  </div>
64
72
  </template>
@@ -10,6 +10,14 @@ export function routes(config = {}) {
10
10
  route({ name: 'user:googleAuthReturn', path: prefix + 'google-auth-return/:action',
11
11
  component: () => import("./GoogleAuthReturn.vue"), props: true }),
12
12
 
13
+ route({ name: 'user:linkedinAuth', path: prefix + 'linkedin-auth/:action',
14
+ component: () => import("./LinkedinAuth.vue"), props: true, meta: { } }),
15
+ route({ name: 'user:linkedinAuthScopes', path: prefix + 'linkedin-auth/:action/:scopes*',
16
+ component: () => import("./LinkedinAuth.vue"), props: true, meta: { } }),
17
+ route({ name: 'user:linkedinAuthReturn', path: prefix + 'linkedin-auth-return/:action',
18
+ component: () => import("./LinkedinAuthReturn.vue"), props: true }),
19
+
20
+
13
21
  route({ name: 'user:signInEmail', path: prefix + 'sign-in-email',
14
22
  component: () => import("./SignInEmail.vue"), meta: { signedOut: true } }),
15
23
  route({ name: 'user:signInFinished', path: prefix + 'sign-in-finished',