@live-change/user-frontend 0.8.14 → 0.8.16

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.
@@ -8,6 +8,7 @@ export {}
8
8
  declare module 'vue' {
9
9
  export interface GlobalComponents {
10
10
  Button: typeof import('primevue/button')['default']
11
+ Divider: typeof import('primevue/divider')['default']
11
12
  RouterLink: typeof import('vue-router')['RouterLink']
12
13
  RouterView: typeof import('vue-router')['RouterView']
13
14
  }
@@ -18,10 +18,20 @@
18
18
  </div>
19
19
  <Button class="p-button-text p-button-plain p-button-rounded mr-1" icon="pi pi-times"
20
20
  v-if="canDelete"
21
- @click="event => disconnect(event, contact.contactType, contact.id)" />
21
+ @click="event => disconnectContact(event, contact)" />
22
+ </li>
23
+ <li v-for="account in accounts"
24
+ class="flex flex-row align-items-center justify-content-between mb-2">
25
+ <div v-if="account.accountType.accountType === 'google'"
26
+ class="flex flex-row align-items-center">
27
+ <i class="pi pi-google mr-2"></i>
28
+ <span class="block text-900 font-medium text-lg">{{ account.email }}</span>
29
+ </div>
30
+ <pre v-else>{{ account }}</pre>
31
+ <Button class="p-button-text p-button-plain p-button-rounded mr-1" icon="pi pi-times"
32
+ v-if="canDelete"
33
+ @click="event => disconnectAccount(event, account, account.email)" />
22
34
  </li>
23
-
24
- <!-- {{ contacts }}-->
25
35
 
26
36
  </ul>
27
37
 
@@ -55,11 +65,16 @@
55
65
  import { useApi, live, usePath, useActions } from '@live-change/vue3-ssr'
56
66
  const api = useApi()
57
67
  const path = usePath()
58
- const clientConfig = api.getServiceDefinition('messageAuthentication')?.clientConfig
68
+ const messageAuthenticationClientConfig = api.getServiceDefinition('messageAuthentication')?.clientConfig
69
+ const contactTypesAvailable = messageAuthenticationClientConfig?.contactTypes || []
70
+
71
+ const userClientConfig = api.getServiceDefinition('user')?.clientConfig
72
+ const accountTypesAvailable = userClientConfig?.remoteAccountTypes || []
59
73
 
60
74
  const messageAuthenticationApi = useActions().messageAuthentication
61
75
 
62
- function disconnect(event, contactType, contact) {
76
+ function disconnectContact(event, contactData) {
77
+ const { contactType, id: contact } = contactData
63
78
  confirm.require({
64
79
  target: event.currentTarget,
65
80
  message: `Do you want to disconnect ${contactType.contactType} account ${contact}?`,
@@ -78,12 +93,30 @@
78
93
  })
79
94
  }
80
95
 
81
- const contactTypesAvailable = clientConfig.contactTypes
96
+ function disconnectAccount(event, contactData, name) {
97
+ const { accountType, id: account } = contactData
98
+ confirm.require({
99
+ target: event.currentTarget,
100
+ message: `Do you want to disconnect ${accountType.accountType} account ${name || account}?`,
101
+ icon: 'pi pi-info-circle',
102
+ acceptClass: 'p-button-danger',
103
+ accept: async () => {
104
+ const upperCaseType = accountType.accountType[0].toUpperCase() + accountType.accountType.slice(1)
105
+ workingZone.addPromise('disconnectAccount', (async () => {
106
+ await api.actions[accountType.accountType+'Authentication']['disconnect'+upperCaseType]({ account })
107
+ toast.add({ severity: 'info', summary: 'Account disconnected', life: 1500 })
108
+ })())
109
+ },
110
+ reject: () => {
111
+ toast.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
112
+ }
113
+ })
114
+ }
82
115
 
83
116
  const contactsTypes = contactTypesAvailable.map(contactType => {
84
117
  const contactTypeUpper = contactType[0].toUpperCase() + contactType.slice(1)
85
118
 
86
- let serviceName = 'myUser'+contactTypeUpper+'s'
119
+ let serviceName = contactType
87
120
  let viewName = 'myUser'+contactTypeUpper+'s'
88
121
  if(!path[serviceName]) { // find service by viewName
89
122
  for(const s in path) {
@@ -93,7 +126,8 @@
93
126
  }
94
127
  }
95
128
  }
96
- console.log('contactType', contactType, 'serviceName', serviceName, 'viewName', viewName)
129
+ //console.log('contactType', contactType, 'serviceName', serviceName, 'viewName', viewName)
130
+ console.log(`path[${serviceName}][${viewName}] =`, path[serviceName][viewName])
97
131
  return {
98
132
  contactType,
99
133
  serviceName,
@@ -103,16 +137,41 @@
103
137
  }
104
138
  })
105
139
 
106
- await Promise.all(contactsTypes.map(async contactType => {
140
+ const accountTypes = accountTypesAvailable.map(accountType => {
141
+ let serviceName = accountType+'Authentication'
142
+ let viewName = 'myUserAccounts'
143
+ console.log('remoteAccountType', accountType, 'serviceName', serviceName, 'viewName', viewName)
144
+ console.log(`path[${serviceName}][${viewName}] =`, path[serviceName][viewName])
145
+ return {
146
+ accountType,
147
+ serviceName,
148
+ viewName,
149
+ path: path[serviceName][viewName]({}),
150
+ accounts: null
151
+ }
152
+ })
153
+
154
+ const contactPromises = contactsTypes.map(async contactType => {
107
155
  contactType.contacts = await live(contactType.path)
108
- }))
156
+ })
157
+
158
+ const accountPromises = accountTypes.map(async accountType => {
159
+ accountType.accounts = await live(accountType.path)
160
+ })
161
+
162
+ await Promise.all([ ...contactPromises, ...accountPromises ])
109
163
 
110
164
  const contacts = computed(() => contactsTypes.map((c,i) => c.contacts.value.map(v => ({
111
165
  contactType: c,
112
166
  ...(v)
113
167
  }))).flat())
114
168
 
115
- const allAccountsCount = computed(() => contacts.value?.length )
169
+ const accounts = computed(() => accountTypes.map((c,i) => c.accounts.value.map(v => ({
170
+ accountType: c,
171
+ ...(v)
172
+ }))).flat())
173
+
174
+ const allAccountsCount = computed(() => contacts.value?.length + accounts.value?.length )
116
175
  const canDelete = computed(() => allAccountsCount.value > 1 )
117
176
 
118
177
  </script>
@@ -4,7 +4,7 @@ export function routes(config = {}) {
4
4
  return [
5
5
 
6
6
  route({ name: 'user:connected', path: prefix + 'connected',
7
- component: () => import("./Connected.vue") }),
7
+ component: () => import("./Connected.vue"), meta: { signedIn: true } }),
8
8
 
9
9
  route({ name: 'user:connect-email', path: prefix + 'connect-email',
10
10
  component: () => import("./ConnectEmail.vue") }),
@@ -3,7 +3,7 @@
3
3
  <div class="surface-card p-4 shadow-2 border-round">
4
4
  <div class="text-center mb-5">
5
5
  <div class="text-900 text-3xl font-medium mb-3">
6
- Identification
6
+ Profile
7
7
  </div>
8
8
  </div>
9
9
 
@@ -11,7 +11,7 @@
11
11
  </router-link>
12
12
  </li>
13
13
  <li v-if="!client.user">
14
- <router-link :to="{ name: 'user:signUp' }"
14
+ <router-link :to="{ name: 'user:signUpEmail' }"
15
15
  v-ripple
16
16
  class="flex px-6 py-2 align-items-center text-600 hover:text-900 hover:surface-100
17
17
  font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple no-underline"
@@ -21,7 +21,7 @@
21
21
  </router-link>
22
22
  </li>
23
23
  <li v-if="!client.user">
24
- <router-link :to="{ name: 'user:signIn' }"
24
+ <router-link :to="{ name: 'user:signInEmail' }"
25
25
  v-ripple
26
26
  class="flex px-6 py-2 align-items-center text-600 hover:text-900 hover:surface-100
27
27
  font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple no-underline"
@@ -12,7 +12,7 @@
12
12
  class="align-items-center flex-grow-1 justify-content-between hidden absolute w-full md:w-auto surface-overlay
13
13
  right-0 top-100 z-1 shadow-2 overflow-x-hidden overflow-y-auto"
14
14
  style="max-height: calc(100vh - 8rem)">
15
- <loading-zone suspense va-if="isMounted">
15
+ <loading-zone suspense>
16
16
  <template v-slot:loading>
17
17
  <div class="flex align-items-center justify-content-center top-0 left-0 notifications-loading">
18
18
  <ProgressSpinner animationDuration=".5s"/>
@@ -51,7 +51,7 @@
51
51
  phone: 'pi-phone'
52
52
  }
53
53
  function contactText(contact, type) {
54
- if(type == 'web') return 'Web'
54
+ if(type === 'web') return 'Web'
55
55
  return contact
56
56
  }
57
57
 
@@ -89,7 +89,7 @@
89
89
  const settings = computed(() => clientConfig.notificationTypes.map(notificationType => {
90
90
  const contacts = allContacts.map(contactsData => contactsData.list.value.map(contact => {
91
91
  const contactType = contactsData.type
92
- const settingSource = computed(() => contact.settings.find(s => s.notificationType == notificationType))
92
+ const settingSource = computed(() => contact.settings.find(s => s.notificationType === notificationType))
93
93
  const setting = synchronized({
94
94
  source: settingSource,
95
95
  update: notificationApi.setOrUpdateContactAndNotificationOwnedNotificationSetting,
@@ -61,7 +61,7 @@ export function installUserRedirects(router, app, config) {
61
61
  if(!client.value.user) {
62
62
  console.log("REDIRECT TO LOGIN BECAUSE PAGE REQUIRES LOGIN!")
63
63
  router.redirectAfterSignIn = to.fullPath
64
- return { name: 'user:signIn' }
64
+ return { name: 'user:signInEmail' }
65
65
  }
66
66
  }
67
67
  if(to?.matched.find(m => m?.meta.signedOut)) {
@@ -70,7 +70,7 @@ export function installUserRedirects(router, app, config) {
70
70
  return { name: 'user:settings' }
71
71
  }
72
72
  }
73
- if(to && to.name == 'user:signIn' && from?.matched.find(m => m?.meta.saveForSignIn)) {
73
+ if(to && to.name === 'user:signInEmail' && from?.matched.find(m => m?.meta.saveForSignIn)) {
74
74
  console.log("SAVE FOR LOGIN", from.fullPath)
75
75
  localStorage.redirectAfterLogin = from.fullPath
76
76
  }
@@ -6,10 +6,10 @@
6
6
  </div>
7
7
 
8
8
  <div class="flex flex-column relative flex-auto">
9
- <div v-if="viewType == 'simple'" class="p-5 flex flex-column flex-auto align-items-center">
9
+ <div v-if="viewType === 'simple'" class="p-5 flex flex-column flex-auto align-items-center">
10
10
  <router-view></router-view>
11
11
  </div>
12
- <template v-if="viewType == 'wide'">
12
+ <template v-if="viewType === 'wide'">
13
13
  <router-view></router-view>
14
14
  </template>
15
15
  </div>
@@ -17,14 +17,15 @@
17
17
  </template>
18
18
 
19
19
  <script setup>
20
- import SettingsMenu from "./SettingsMenu.vue"
21
20
 
22
- import { computed } from 'vue'
21
+ import SettingsMenu from "./SettingsMenu.vue"
23
22
 
24
- import { useRoute } from 'vue-router'
25
- const route = useRoute()
23
+ import { computed } from 'vue'
26
24
 
27
- const viewType = computed(() => route.meta.viewType ?? 'simple' )
25
+ import { useRoute } from 'vue-router'
26
+ const route = useRoute()
27
+
28
+ const viewType = computed(() => route.meta.viewType ?? 'simple' )
28
29
 
29
30
  </script>
30
31
 
@@ -0,0 +1,122 @@
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">Google authentication</div>
7
+ </div>
8
+
9
+ <div v-if="state === 'canceled'" class="text-center">
10
+ <div>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="googleAuth" label="Try again" icon="pi pi-google" 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 a new 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 { loadGoogleAuth2 } from "../utils/googleApi.js"
36
+ import { defineProps, toRefs, ref, onMounted, inject } from 'vue'
37
+
38
+ import { useApi } from "@live-change/vue3-ssr"
39
+ const api = useApi()
40
+
41
+ import { useToast } from 'primevue/usetoast'
42
+ const toast = useToast()
43
+
44
+ const workingZone = inject('workingZone')
45
+
46
+ import { useRouter } from 'vue-router'
47
+ const router = useRouter()
48
+
49
+ const props = defineProps({
50
+ action: {
51
+ type: String,
52
+ default: 'signInOrSignUp'
53
+ }
54
+ })
55
+
56
+ const { action } = toRefs(props)
57
+ const state = ref('waiting')
58
+ const error = ref(null)
59
+
60
+ async function googleAuth() {
61
+ state.value = 'waiting'
62
+ const auth = await loadGoogleAuth2()
63
+ const googleRedirectUri = document.location.protocol + '//' + document.location.host
64
+ + router.resolve({ name: 'user:googleAuthReturn', params: { action: action.value } }).href
65
+ const response = await (auth.signIn({
66
+ scope: 'profile email'
67
+ }).catch(error => {
68
+ if(error.error === 'popup_blocked_by_browser') {
69
+ return auth.signIn({
70
+ scope: 'profile email',
71
+ ux_mode: 'redirect',
72
+ redirect_uri: googleRedirectUri
73
+ })
74
+ }
75
+ if(error.error === 'popup_closed_by_user') {
76
+ toast.add({ severity: 'warning', summary: 'Canceled', detail: 'You closed login window', life: 3000 })
77
+ state.value = 'canceled'
78
+ return
79
+ }
80
+ throw error
81
+ }))
82
+
83
+ state.value = 'working'
84
+ try {
85
+ const result = await workingZone.addPromise(`google ${action.value}`,
86
+ api.command(['googleAuthentication', action.value], {
87
+ accessToken: response.getAuthResponse().id_token
88
+ })
89
+ )
90
+ //console.log("GAUTH RESULT", result)
91
+ const { action: actionDone, user } = result
92
+ while(api.client.value.user !== result.user) {
93
+ await new Promise(resolve => setTimeout(resolve, 100))
94
+ }
95
+ if(actionDone === 'signIn') {
96
+ router.push({ name: 'user:signInFinished' })
97
+ } else if(actionDone === 'signUp') {
98
+ router.push({ name: 'user:signUpFinished' })
99
+ } else {
100
+ console.error("Unknown action", actionDone)
101
+ }
102
+ } catch(error) {
103
+ console.error("Google auth error", error)
104
+ toast.add({ severity: 'error', summary: 'Error', detail: 'Error during google authentication', life: 3000 })
105
+ state.value = 'error'
106
+ error.value = error
107
+ }
108
+ }
109
+
110
+ async function back() {
111
+ router.go(-1)
112
+ }
113
+
114
+ onMounted(() => {
115
+ googleAuth()
116
+ })
117
+
118
+ </script>
119
+
120
+ <style scoped>
121
+
122
+ </style>
@@ -0,0 +1,104 @@
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">Google authentication</div>
7
+ </div>
8
+
9
+ <div v-if="state === 'canceled'" class="text-center">
10
+ <div>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
+ </div>
14
+ </div>
15
+ <div v-else-if="state === 'working'" class="text-center">
16
+ Waiting for server...
17
+ </div>
18
+ <div v-else-if="state === 'error'" class="text-center">
19
+ <div>Error during authentication</div>
20
+ <div>{{ error }}</div>
21
+ </div>
22
+ <div v-else>
23
+ Unknown authentication state: {{ state }}
24
+ </div>
25
+
26
+ </div>
27
+ </div>
28
+ </template>
29
+
30
+ <script setup>
31
+ import { loadGoogleAuth2 } from "../utils/googleApi.js"
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 } from 'vue-router'
43
+ const router = useRouter()
44
+
45
+ const props = defineProps({
46
+ action: {
47
+ type: String,
48
+ default: 'signInOrSignUp'
49
+ }
50
+ })
51
+
52
+ const { action } = toRefs(props)
53
+ const state = ref('waiting')
54
+ const error = ref(null)
55
+
56
+
57
+ onMounted(async () => {
58
+ const id_token = decodeURIComponent(
59
+ document.location.hash
60
+ .slice(1)
61
+ .split('&')
62
+ .map(p => p.split('='))
63
+ .find(a => a[0] === 'id_token')
64
+ [1]
65
+ )
66
+ if(!id_token) {
67
+ state.value = 'canceled'
68
+ return
69
+ }
70
+ try {
71
+ const result = await workingZone.addPromise(`google ${action.value}`,
72
+ api.command(['googleAuthentication', action.value], {
73
+ accessToken: id_token
74
+ })
75
+ )
76
+ console.log("GAUTH RESULT", result)
77
+ const { action: actionDone, user } = result
78
+ while(api.client.value.user !== result.user) {
79
+ await new Promise(resolve => setTimeout(resolve, 100))
80
+ }
81
+ if(actionDone === 'signIn') {
82
+ router.push({ name: 'user:signInFinished' })
83
+ } else if(actionDone === 'signUp') {
84
+ router.push({ name: 'user:signUpFinished' })
85
+ } else {
86
+ console.error("Unknown action", actionDone)
87
+ }
88
+ } catch(error) {
89
+ console.error("Google auth error", error)
90
+ toast.add({ severity: 'error', summary: 'Error', detail: 'Error during google authentication', life: 3000 })
91
+ state.value = 'error'
92
+ error.value = error
93
+ }
94
+ })
95
+
96
+ async function back() {
97
+ router.go(-1)
98
+ }
99
+
100
+ </script>
101
+
102
+ <style scoped>
103
+
104
+ </style>
@@ -1,10 +1,11 @@
1
1
  <template>
2
2
  <div class="w-full lg:w-6 md:w-9" v-shared-element:form="{ duration: '300ms', includeChildren: true }">
3
3
  <div class="surface-card p-4 shadow-2 border-round">
4
+
4
5
  <div class="text-center mb-5">
5
6
  <div class="text-900 text-3xl font-medium mb-3">Welcome Back</div>
6
7
  <span class="text-600 font-medium line-height-3">Don't have an account?</span>
7
- <router-link :to="{ name: 'user:signUp' }"
8
+ <router-link :to="{ name: 'user:signUpEmail' }"
8
9
  class="font-medium no-underline ml-2 text-blue-500 cursor-pointer">
9
10
  Create today!</router-link>
10
11
  </div>
@@ -32,7 +33,7 @@
32
33
 
33
34
  <div class="flex align-items-center justify-content-between mb-6">
34
35
  <div class="flex align-items-center">
35
- <Checkbox id="rememberme" :binary="true" class="mr-2"></Checkbox>
36
+ <Checkbox id="rememberme" :binary="true" class="mr-2" />
36
37
  <label for="rememberme">Remember me</label>
37
38
  </div>
38
39
  <router-link :to="{ name: 'user:resetPassword' }"
@@ -43,14 +44,21 @@
43
44
 
44
45
  <Button label="Sign In" icon="pi pi-user" class="w-full" type="submit"></Button>
45
46
 
46
- <Divider align="center" class="my-4">
47
- <span class="text-600 font-normal text-sm">OR</span>
48
- </Divider>
47
+ </command-form>
48
+
49
+ <Divider align="center" class="my-4">
50
+ <span class="text-600 font-normal text-sm">OR</span>
51
+ </Divider>
49
52
 
50
- <Button label="Sign In with GitHub" icon="pi pi-github" class="w-full p-button-secondary mb-2"></Button>
51
- <Button label="Sign In with Google" icon="pi pi-google" class="w-full p-button-secondary mb-1"></Button>
53
+ <!-- <Button label="Sign In with GitHub" icon="pi pi-github" class="w-full p-button-secondary mb-2" />-->
54
+ <router-link :to="{ name: 'user:googleAuth', params: { action: 'signInOrSignUp' } }" class="no-underline">
55
+ <Button
56
+ label="Sign In with Google"
57
+ icon="pi pi-google"
58
+ class="w-full p-button-secondary mb-1"
59
+ />
60
+ </router-link>
52
61
 
53
- </command-form>
54
62
  </div>
55
63
  </div>
56
64
  </template>
@@ -72,7 +80,7 @@
72
80
 
73
81
  function handleDone({ parameters, result }) {
74
82
  console.log("DONE RESULT", result)
75
- if(result.type == 'sent') {
83
+ if(result.type === 'sent') {
76
84
  const { authentication } = result
77
85
  router.push({
78
86
  name: 'user:sent',
@@ -15,7 +15,9 @@
15
15
  const isMounted = ref(false)
16
16
  onMounted(() => isMounted.value = true)
17
17
 
18
- import { actions } from '@live-change/vue3-ssr'
18
+ import { actions, useApi } from '@live-change/vue3-ssr'
19
+ const api = useApi()
20
+
19
21
  import { inject } from 'vue'
20
22
  import { useRouter } from 'vue-router'
21
23
  const router = useRouter()
@@ -27,7 +29,10 @@
27
29
  if(typeof window != 'undefined') {
28
30
  workingZone.addPromise('signOut', (async () => {
29
31
  await signOut({})
30
- router.push({name: 'user:signOutFinished'})
32
+ while(api.client.value.user) {
33
+ await new Promise(resolve => setTimeout(resolve, 100))
34
+ }
35
+ router.push({ name: 'user:signOutFinished' })
31
36
  })())
32
37
  }
33
38
  </script>
@@ -4,7 +4,7 @@
4
4
  <div class="text-center mb-5">
5
5
  <div class="text-900 text-3xl font-medium mb-3">Sign Up</div>
6
6
  <span class="text-600 font-medium line-height-3">Already have an account?</span>
7
- <router-link :to="{ name: 'user:signUp' }"
7
+ <router-link :to="{ name: 'user:signUpEmail' }"
8
8
  class="font-medium no-underline ml-2 text-blue-500 cursor-pointer">
9
9
  Sign in</router-link>
10
10
  </div>
@@ -22,18 +22,30 @@
22
22
  <small v-if="data.emailError" id="email-help" class="p-error">{{ data.emailError }}</small>
23
23
  </div>
24
24
 
25
- <Button label="Sign Up with email" icon="pi pi-user" class="w-full" type="submit"></Button>
25
+ <Button label="Sign Up with email" icon="pi pi-user" class="w-full" type="submit" />
26
26
 
27
27
  </command-form>
28
+
29
+ <Divider align="center" class="my-4">
30
+ <span class="text-600 font-normal text-sm">OR</span>
31
+ </Divider>
32
+
33
+ <!-- <Button label="Sign In with GitHub" icon="pi pi-github" class="w-full p-button-secondary mb-2" />-->
34
+ <router-link :to="{ name: 'user:googleAuth', params: { action: 'signInOrSignUp' } }" class="no-underline">
35
+ <Button
36
+ label="Sign Up with Google"
37
+ icon="pi pi-google"
38
+ class="w-full p-button-secondary mb-1"
39
+ />
40
+ </router-link>
41
+
28
42
  </div>
29
43
  </div>
30
44
  </template>
31
45
 
32
46
  <script setup>
33
47
  import InputText from "primevue/inputtext"
34
- import Checkbox from "primevue/checkbox"
35
48
  import Button from "primevue/button"
36
- import Divider from "primevue/divider"
37
49
 
38
50
  import { useRouter } from 'vue-router'
39
51
  const router = useRouter()
@@ -97,16 +97,19 @@
97
97
  form.value.addValidator('passwordHash', () => {
98
98
  const value = form.value.getFieldValue('passwordHash')
99
99
  console.log("PASSWORDS MATCH?", secondPassword.value, value)
100
- if(value != secondPassword.value) return "passwordsNotMatch"
100
+ if(value !== secondPassword.value) return "passwordsNotMatch"
101
101
  })
102
102
  })
103
103
 
104
- const [passwordExists, emails] = await Promise.all([
104
+ const [passwordExists, emails, phones] = await Promise.all([
105
105
  live(path().passwordAuthentication.myUserPasswordAuthenticationExists()),
106
- live(path().email.myUserEmails())
106
+ live(path().email?.myUserEmails()),
107
+ live(path().phone?.myUserPhones())
107
108
  ])
108
109
 
109
- const needPassword = computed(() => (!passwordExists.value && emails.value?.length > 0))
110
+ const needPassword = computed(() => (!passwordExists.value
111
+ && (emails.value?.length > 0 || phones.value?.length > 0)
112
+ ))
110
113
 
111
114
  function handleDone({ parameters, result }) {
112
115
  console.log("FORM DONE", parameters, result)
@@ -3,20 +3,25 @@ export function routes(config = {}) {
3
3
 
4
4
  return [
5
5
 
6
- route({ name: 'user:signIn', path: prefix + 'sign-in',
7
- component: () => import("./SignIn.vue") }),
6
+ route({ name: 'user:googleAuth', path: prefix + 'google-auth/:action',
7
+ component: () => import("./GoogleAuth.vue"), props: true, meta: { signedOut: true } }),
8
+ route({ name: 'user:googleAuthReturn', path: prefix + 'google-auth-return/:action',
9
+ component: () => import("./GoogleAuthReturn.vue"), props: true }),
10
+
11
+ route({ name: 'user:signInEmail', path: prefix + 'sign-in-email',
12
+ component: () => import("./SignInEmail.vue"), meta: { signedOut: true } }),
8
13
  route({ name: 'user:signInFinished', path: prefix + 'sign-in-finished',
9
14
  component: () => import("./SignInFinished.vue"), meta: { signedIn: true } }),
10
15
 
11
- route({ name: 'user:signUp', path: prefix + 'sign-up',
12
- component: () => import("./SignUp.vue") }),
16
+ route({ name: 'user:signUpEmail', path: prefix + 'sign-up-email',
17
+ component: () => import("./SignUpEmail.vue"), meta: { signedOut: true } }),
13
18
  route({ name: 'user:signUpFinished', path: prefix + 'sign-up-finished',
14
19
  component: () => import("./SignUpFinished.vue"), meta: { signedIn: true } }),
15
20
 
16
21
  route({ name: 'user:signOut', path: prefix + 'sign-out',
17
22
  component: () => import("./SignOut.vue") }),
18
23
  route({ name: 'user:signOutFinished', path: prefix + 'sign-out-finished',
19
- component: () => import("./SignOutFinished.vue") }),
24
+ component: () => import("./SignOutFinished.vue"), meta: { signedOut: true } }),
20
25
 
21
26
  ]
22
27
  }
@@ -0,0 +1,50 @@
1
+ let googPromise
2
+
3
+ function loadGoogle() {
4
+ if(googPromise) return googPromise
5
+ let resolved = false
6
+ googPromise = new Promise((resolve, reject) => {
7
+ if (typeof window != 'undefined') {
8
+ window.googAsyncInit = function () {
9
+ if(resolved) return
10
+ resolved = true
11
+ resolve(gapi)
12
+ }
13
+
14
+ ;(function (d, s, id) {
15
+ const fjs = d.getElementsByTagName(s)[0]
16
+ if (d.getElementById(id)) return
17
+ const js = d.createElement(s)
18
+ js.id = id
19
+ js.src = "https://apis.google.com/js/platform.js?onload=googAsyncInit"
20
+ fjs.parentNode.insertBefore(js, fjs)
21
+ }(document, 'script', 'google-jssdk'))
22
+ } else {
23
+ reject('unavailable')
24
+ }
25
+ })
26
+ return googPromise
27
+ }
28
+
29
+ let googAuth2Promise
30
+
31
+ async function loadGoogleAuth2() {
32
+ if(googAuth2Promise) return googAuth2Promise
33
+ let gapi = await loadGoogle()
34
+ let resolved = false
35
+ googAuth2Promise = new Promise((resolve, reject) => {
36
+ gapi.load('auth2', function() {
37
+ if(resolved) return
38
+ resolved = true
39
+ let client_id = ENV_GOOGLE_CLIENT_ID
40
+ resolve(gapi.auth2.init({
41
+ client_id
42
+ }))
43
+ })
44
+ })
45
+ return googAuth2Promise
46
+ }
47
+
48
+
49
+
50
+ export { loadGoogle, loadGoogleAuth2 }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/user-frontend",
3
- "version": "0.8.14",
3
+ "version": "0.8.16",
4
4
  "scripts": {
5
5
  "memDev": "node --inspect --expose-gc server/start.js memDev --enableSessions --initScript ./init.js --dbAccess",
6
6
  "localDevInit": "rm tmp.db; node server/start.js localDev --enableSessions --initScript ./init.js",
@@ -17,34 +17,35 @@
17
17
  "build:client": "cd front; vite build --ssrManifest --outDir dist/client",
18
18
  "build:server": "cd front; vite build --ssr src/entry-server.js --outDir dist/server",
19
19
  "generate": "vite build --ssrManifest --outDir dist/static && yarn build:server && node prerender",
20
- "debug": "node --inspect-brk server"
20
+ "debug": "node --inspect-brk server",
21
+ "describe": "node server/start.js describe"
21
22
  },
22
23
  "type": "module",
23
24
  "dependencies": {
24
- "@live-change/cli": "^0.8.14",
25
- "@live-change/dao": "^0.8.14",
26
- "@live-change/dao-vue3": "^0.8.14",
27
- "@live-change/dao-websocket": "^0.8.14",
28
- "@live-change/email-service": "^0.8.14",
29
- "@live-change/framework": "^0.8.14",
30
- "@live-change/identicon-service": "^0.8.14",
31
- "@live-change/image-frontend": "^0.8.14",
32
- "@live-change/message-authentication-service": "^0.8.14",
33
- "@live-change/notification-service": "^0.8.14",
34
- "@live-change/password-authentication-service": "^0.8.14",
35
- "@live-change/pattern": "^0.8.14",
36
- "@live-change/secret-code-service": "^0.8.14",
37
- "@live-change/secret-link-service": "^0.8.14",
38
- "@live-change/security-frontend": "^0.8.14",
39
- "@live-change/security-service": "^0.8.14",
40
- "@live-change/session-service": "^0.8.14",
41
- "@live-change/timer-service": "^0.8.14",
42
- "@live-change/upload-service": "^0.8.14",
43
- "@live-change/user-identification-service": "^0.8.14",
44
- "@live-change/user-service": "^0.8.14",
45
- "@live-change/vue3-components": "^0.8.14",
46
- "@live-change/vue3-ssr": "^0.8.14",
47
- "@vueuse/core": "^10.7.2",
25
+ "@live-change/cli": "^0.8.16",
26
+ "@live-change/dao": "^0.8.16",
27
+ "@live-change/dao-vue3": "^0.8.16",
28
+ "@live-change/dao-websocket": "^0.8.16",
29
+ "@live-change/email-service": "^0.8.16",
30
+ "@live-change/framework": "^0.8.16",
31
+ "@live-change/identicon-service": "^0.8.16",
32
+ "@live-change/image-frontend": "^0.8.16",
33
+ "@live-change/message-authentication-service": "^0.8.16",
34
+ "@live-change/notification-service": "^0.8.16",
35
+ "@live-change/password-authentication-service": "^0.8.16",
36
+ "@live-change/pattern": "^0.8.16",
37
+ "@live-change/secret-code-service": "^0.8.16",
38
+ "@live-change/secret-link-service": "^0.8.16",
39
+ "@live-change/security-frontend": "^0.8.16",
40
+ "@live-change/security-service": "^0.8.16",
41
+ "@live-change/session-service": "^0.8.16",
42
+ "@live-change/timer-service": "^0.8.16",
43
+ "@live-change/upload-service": "^0.8.16",
44
+ "@live-change/user-identification-service": "^0.8.16",
45
+ "@live-change/user-service": "^0.8.16",
46
+ "@live-change/vue3-components": "^0.8.16",
47
+ "@live-change/vue3-ssr": "^0.8.16",
48
+ "@vueuse/core": "^10.9.0",
48
49
  "codeceptjs-assert": "^0.0.5",
49
50
  "codeceptjs-video-helper": "0.1.3",
50
51
  "compression": "^1.7.4",
@@ -64,7 +65,7 @@
64
65
  "wtfnode": "^0.9.1"
65
66
  },
66
67
  "devDependencies": {
67
- "@live-change/codeceptjs-helper": "^0.8.14",
68
+ "@live-change/codeceptjs-helper": "^0.8.16",
68
69
  "codeceptjs": "^3.5.12",
69
70
  "generate-password": "1.7.1",
70
71
  "playwright": "^1.41.2",
@@ -75,5 +76,5 @@
75
76
  "author": "",
76
77
  "license": "BSD-3-Clause",
77
78
  "description": "",
78
- "gitHead": "d81b573fb8891746ae1c67f4f68558123c9f85f7"
79
+ "gitHead": "b8ac84cbe1a8c435c7b5003dfc64ddc41a4e15a6"
79
80
  }
@@ -2,6 +2,7 @@ import App from "@live-change/framework"
2
2
  const app = App.app()
3
3
 
4
4
  const contactTypes = ['email', 'phone']
5
+ const remoteAccountTypes = ['google']
5
6
 
6
7
  import securityConfig from './security.config.js'
7
8
 
@@ -18,7 +19,8 @@ app.config = {
18
19
  },
19
20
  {
20
21
  name: 'user',
21
- path: '@live-change/user-service'
22
+ path: '@live-change/user-service',
23
+ remoteAccountTypes
22
24
  },
23
25
  {
24
26
  name: 'email',
@@ -50,6 +52,10 @@ app.config = {
50
52
  contactTypes,
51
53
  signInWithoutPassword: true
52
54
  },
55
+ {
56
+ name: 'googleAuthentication',
57
+ path: '@live-change/google-authentication-service',
58
+ },
53
59
  {
54
60
  name: 'security',
55
61
  path: '@live-change/security-service',
@@ -13,6 +13,7 @@ import image from '@live-change/image-service'
13
13
  import secretCode from '@live-change/secret-code-service'
14
14
  import secretLink from '@live-change/secret-link-service'
15
15
  import messageAuthentication from '@live-change/message-authentication-service'
16
+ import googleAuthentication from '@live-change/google-authentication-service'
16
17
 
17
18
  import backup from '@live-change/backup-service'
18
19
  import init from './init.js'
@@ -32,6 +33,7 @@ export {
32
33
  secretCode,
33
34
  secretLink,
34
35
  messageAuthentication,
36
+ googleAuthentication,
35
37
  localeSettings,
36
38
  backup,
37
39
  init
@@ -1,12 +0,0 @@
1
- // vite.config.js
2
- import { defineConfig } from "file:///home/m8/IdeaProjects/live-change/node_modules/vite/dist/node/index.js";
3
- import baseViteConfig from "file:///home/m8/IdeaProjects/live-change/live-change-stack/frontend/frontend-base/vite-config.js";
4
- var vite_config_default = defineConfig(async ({ command, mode }) => {
5
- return {
6
- ...await baseViteConfig({ command, mode })
7
- };
8
- });
9
- export {
10
- vite_config_default as default
11
- };
12
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcuanMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9tOC9JZGVhUHJvamVjdHMvbGl2ZS1jaGFuZ2UvbGl2ZS1jaGFuZ2Utc3RhY2svZnJvbnRlbmQvdXNlci1mcm9udGVuZC9mcm9udFwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL2hvbWUvbTgvSWRlYVByb2plY3RzL2xpdmUtY2hhbmdlL2xpdmUtY2hhbmdlLXN0YWNrL2Zyb250ZW5kL3VzZXItZnJvbnRlbmQvZnJvbnQvdml0ZS5jb25maWcuanNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL2hvbWUvbTgvSWRlYVByb2plY3RzL2xpdmUtY2hhbmdlL2xpdmUtY2hhbmdlLXN0YWNrL2Zyb250ZW5kL3VzZXItZnJvbnRlbmQvZnJvbnQvdml0ZS5jb25maWcuanNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xuXG5pbXBvcnQgYmFzZVZpdGVDb25maWcgZnJvbSAnQGxpdmUtY2hhbmdlL2Zyb250ZW5kLWJhc2Uvdml0ZS1jb25maWcuanMnXG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyhhc3luYyAoeyBjb21tYW5kLCBtb2RlIH0pID0+IHtcbiAgcmV0dXJuIHtcbiAgICAuLi4oYXdhaXQgYmFzZVZpdGVDb25maWcoeyBjb21tYW5kLCBtb2RlIH0pKSxcblxuXG4gIH1cbn0pXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQWthLFNBQVMsb0JBQW9CO0FBRS9iLE9BQU8sb0JBQW9CO0FBRTNCLElBQU8sc0JBQVEsYUFBYSxPQUFPLEVBQUUsU0FBUyxLQUFLLE1BQU07QUFDdkQsU0FBTztBQUFBLElBQ0wsR0FBSSxNQUFNLGVBQWUsRUFBRSxTQUFTLEtBQUssQ0FBQztBQUFBLEVBRzVDO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K