@live-change/user-frontend 0.0.3
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/LICENSE +21 -0
- package/e2e/codecept.conf.js +60 -0
- package/e2e/connectEmailCode.test.js +61 -0
- package/e2e/connectEmailLink.test.js +60 -0
- package/e2e/delete.test.js +44 -0
- package/e2e/disconnectEmail.test.js +42 -0
- package/e2e/resetPasswordWithEmailCode.test.js +62 -0
- package/e2e/resetPasswordWithEmailLink.test.js +62 -0
- package/e2e/setPassword.test.js +70 -0
- package/e2e/signInEmailCode.test.js +52 -0
- package/e2e/signInEmailLink.test.js +52 -0
- package/e2e/signInEmailPassword.test.js +47 -0
- package/e2e/signOut.test.js +41 -0
- package/e2e/signUpEmailCode.test.js +41 -0
- package/e2e/signUpEmailLink.test.js +41 -0
- package/e2e/steps.d.ts +12 -0
- package/e2e/steps_file.js +89 -0
- package/front/index.html +11 -0
- package/front/public/favicon.ico +0 -0
- package/front/public/images/empty-photo.svg +38 -0
- package/front/public/images/empty-user-photo.svg +33 -0
- package/front/public/images/logo.svg +34 -0
- package/front/public/images/logo128.png +0 -0
- package/front/src/App.vue +31 -0
- package/front/src/Index.vue +14 -0
- package/front/src/NavBar.vue +103 -0
- package/front/src/SettingsTabs.vue +48 -0
- package/front/src/connected/Connect.vue +58 -0
- package/front/src/connected/ConnectFinished.vue +16 -0
- package/front/src/connected/Connected.vue +84 -0
- package/front/src/connected/routes.js +16 -0
- package/front/src/delete/Delete.vue +53 -0
- package/front/src/delete/DeleteFeedbackSent.vue +16 -0
- package/front/src/delete/DeleteFinished.vue +32 -0
- package/front/src/delete/routes.js +16 -0
- package/front/src/entry-client.js +6 -0
- package/front/src/entry-server.js +6 -0
- package/front/src/identification/IdentificationSettings.vue +116 -0
- package/front/src/identification/ObjectIdentification.vue +36 -0
- package/front/src/identification/UserIdentification.vue +101 -0
- package/front/src/identification/routes.js +12 -0
- package/front/src/message-auth/ConnectEmail.vue +105 -0
- package/front/src/message-auth/MessageLink.vue +95 -0
- package/front/src/message-auth/MessageSent.vue +103 -0
- package/front/src/message-auth/ResetPasswordEmail.vue +105 -0
- package/front/src/message-auth/SignInEmail.vue +105 -0
- package/front/src/message-auth/SignUpEmail.vue +105 -0
- package/front/src/message-auth/routes.js +25 -0
- package/front/src/notifications/NotificationButtons.vue +70 -0
- package/front/src/notifications/NotificationListPage.vue +22 -0
- package/front/src/notifications/NotificationsIcon.vue +75 -0
- package/front/src/notifications/NotificationsList.vue +144 -0
- package/front/src/notifications/NotificationsSettings.vue +117 -0
- package/front/src/notifications/SimpleNotification.vue +34 -0
- package/front/src/notifications/TestNotification.vue +25 -0
- package/front/src/notifications/UnknownNotification.vue +25 -0
- package/front/src/notifications/notificationTypes.js +11 -0
- package/front/src/notifications/routes.js +37 -0
- package/front/src/password/ChangePassword.vue +106 -0
- package/front/src/password/ChangePasswordFinished.vue +16 -0
- package/front/src/password/ResetPassword.vue +56 -0
- package/front/src/password/ResetPasswordFinished.vue +16 -0
- package/front/src/password/ResetPasswordForm.vue +118 -0
- package/front/src/password/routes.js +41 -0
- package/front/src/router.js +90 -0
- package/front/src/settings/Settings.vue +33 -0
- package/front/src/settings/SettingsIndex.vue +22 -0
- package/front/src/settings/SettingsMenu.vue +81 -0
- package/front/src/settings/SettingsMenuItem.vue +35 -0
- package/front/src/sign/SignIn.vue +93 -0
- package/front/src/sign/SignInFinished.vue +27 -0
- package/front/src/sign/SignOut.vue +37 -0
- package/front/src/sign/SignOutFinished.vue +16 -0
- package/front/src/sign/SignUp.vue +51 -0
- package/front/src/sign/SignUpFinished.vue +16 -0
- package/front/src/sign/routes.js +24 -0
- package/front/vite.config.js +11 -0
- package/index.js +11 -0
- package/package.json +87 -0
- package/server/init.js +53 -0
- package/server/security.config.js +53 -0
- package/server/services.config.js +74 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full lg:w-6 md:w-9">
|
|
3
|
+
|
|
4
|
+
<ConfirmPopup v-if="isMounted" />
|
|
5
|
+
<Toast v-if="isMounted" />
|
|
6
|
+
|
|
7
|
+
<div class="surface-card border-round shadow-2 p-4">
|
|
8
|
+
<div class="text-900 font-medium mb-3 text-xl">Connected accounts</div>
|
|
9
|
+
|
|
10
|
+
<ul class="list-none p-0 m-0 mt-5 mb-4">
|
|
11
|
+
|
|
12
|
+
<li v-for="connection in emails"
|
|
13
|
+
class="flex flex-row align-items-center justify-content-between mb-2">
|
|
14
|
+
<div class="flex flex-row align-items-center">
|
|
15
|
+
<i class="pi pi-envelope mr-2"></i>
|
|
16
|
+
<span class="block text-900 font-medium text-lg">{{ connection.email }}</span>
|
|
17
|
+
</div>
|
|
18
|
+
<Button class="p-button-text p-button-plain p-button-rounded mr-1" icon="pi pi-times"
|
|
19
|
+
v-if="canDelete"
|
|
20
|
+
@click="event => disconnect(event, 'email', connection.email)" />
|
|
21
|
+
</li>
|
|
22
|
+
|
|
23
|
+
</ul>
|
|
24
|
+
|
|
25
|
+
<div class="flex flex-row">
|
|
26
|
+
<router-link :to="{ name: 'user:connect' }" class="mr-2 no-underline">
|
|
27
|
+
<Button label="Connect Account" icon="pi pi-user-plus" class="p-button-lg" id="connect"></Button>
|
|
28
|
+
</router-link>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script setup>
|
|
36
|
+
import Button from "primevue/button"
|
|
37
|
+
import SettingsTabs from "../SettingsTabs.vue"
|
|
38
|
+
|
|
39
|
+
import { ref, onMounted, onUnmounted, inject, computed } from 'vue'
|
|
40
|
+
import ConfirmPopup from 'primevue/confirmpopup'
|
|
41
|
+
import Toast from 'primevue/toast'
|
|
42
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
43
|
+
const confirm = useConfirm()
|
|
44
|
+
import { useToast } from 'primevue/usetoast'
|
|
45
|
+
const toast = useToast()
|
|
46
|
+
let isMounted = ref(false)
|
|
47
|
+
onMounted(() => isMounted.value = true)
|
|
48
|
+
onUnmounted(() => isMounted.value = false)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
const workingZone = inject('workingZone')
|
|
52
|
+
|
|
53
|
+
import { path, live, actions } from '@live-change/vue3-ssr'
|
|
54
|
+
const messageAuthenticationApi = actions().messageAuthentication
|
|
55
|
+
|
|
56
|
+
function disconnect(event, contactType, contact) {
|
|
57
|
+
confirm.require({
|
|
58
|
+
target: event.currentTarget,
|
|
59
|
+
message: `Do you want to disconnect ${contactType} account ${contact}?`,
|
|
60
|
+
icon: 'pi pi-info-circle',
|
|
61
|
+
acceptClass: 'p-button-danger',
|
|
62
|
+
accept: async () => {
|
|
63
|
+
const upperCaseType = contactType[0].toUpperCase() + contactType.slice(1)
|
|
64
|
+
workingZone.addPromise('disconnectContact', (async () => {
|
|
65
|
+
await messageAuthenticationApi['disconnect'+upperCaseType]({ [contactType]: contact })
|
|
66
|
+
toast.add({ severity: 'info', summary: 'Account disconnected', life: 1500 })
|
|
67
|
+
})())
|
|
68
|
+
},
|
|
69
|
+
reject: () => {
|
|
70
|
+
toast.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const emails = await live(path().email.myUserEmails({}))
|
|
76
|
+
|
|
77
|
+
const allAccountsCount = computed(() => emails.value?.length )
|
|
78
|
+
const canDelete = computed(() => allAccountsCount.value > 1 )
|
|
79
|
+
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<style>
|
|
83
|
+
|
|
84
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function routes(config = {}) {
|
|
2
|
+
const { prefix = '/', route = (r) => r } = config
|
|
3
|
+
|
|
4
|
+
return [
|
|
5
|
+
|
|
6
|
+
route({ name: 'user:connected', path: prefix + 'connected',
|
|
7
|
+
component: () => import("./Connected.vue") }),
|
|
8
|
+
route({ name: 'user:connect', path: prefix + 'connect',
|
|
9
|
+
component: () => import("./Connect.vue") }),
|
|
10
|
+
route({ name: 'user:connectFinished', path: prefix + 'connect-finished',
|
|
11
|
+
component: () => import("./ConnectFinished.vue") }),
|
|
12
|
+
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default routes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full lg:w-6 md:w-9" v-shared-element:form="{ duration: '300ms', includeChildren: true }">
|
|
3
|
+
|
|
4
|
+
<div class="surface-card p-4 shadow-2 border-round">
|
|
5
|
+
<div class="text-center mb-5">
|
|
6
|
+
<div class="text-900 text-3xl font-medium mb-3">Delete account</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div>
|
|
10
|
+
<p>
|
|
11
|
+
Account deletion is irreversible, check the box below only if you are
|
|
12
|
+
100% sure that you want to delete your account.
|
|
13
|
+
</p>
|
|
14
|
+
<div class="p-field-checkbox mb-3">
|
|
15
|
+
<Checkbox id="deleteCheckbox" v-model="wantDelete" :binary="true" />
|
|
16
|
+
<label for="deleteCheckbox" class="ml-2">I want to delete my account.</label>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<Button id="delete" label="Delete account" icon="pi pi-user-minus" class="p-button-lg"
|
|
20
|
+
:disabled="!wantDelete" @click="deleteUser" />
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup>
|
|
27
|
+
import Checkbox from "primevue/checkbox"
|
|
28
|
+
import Button from "primevue/button"
|
|
29
|
+
|
|
30
|
+
import { actions } from "@live-change/vue3-ssr"
|
|
31
|
+
import { inject, ref } from 'vue'
|
|
32
|
+
import { useRouter } from 'vue-router'
|
|
33
|
+
const router = useRouter()
|
|
34
|
+
|
|
35
|
+
const workingZone = inject('workingZone')
|
|
36
|
+
|
|
37
|
+
const wantDelete = ref(false)
|
|
38
|
+
|
|
39
|
+
const { deleteMe } = actions().user
|
|
40
|
+
|
|
41
|
+
function deleteUser() {
|
|
42
|
+
if(!wantDelete.value) return
|
|
43
|
+
workingZone.addPromise('deleteMe', (async () => {
|
|
44
|
+
await deleteMe()
|
|
45
|
+
router.push({ name: 'user:deleteFinished' })
|
|
46
|
+
})())
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<style>
|
|
52
|
+
|
|
53
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
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 border-round shadow-2 p-4">
|
|
4
|
+
<div class="text-900 font-medium mb-3 text-xl mb-4">Feedback sent</div>
|
|
5
|
+
<p class="mt-0 p-0 line-height-3">Thank you for your feedback.</p>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup>
|
|
11
|
+
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<style>
|
|
15
|
+
|
|
16
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
<div class="text-center mb-5">
|
|
5
|
+
<div class="text-900 text-3xl font-medium mb-3">Account deleted</div>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="mb-3">
|
|
9
|
+
Account has been deleted, please leave feedback why you are leaving us.
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<Textarea class="w-full" :autoResize="true" rows="4" cols="30" />
|
|
13
|
+
<div class="flex flex-row align-items-end">
|
|
14
|
+
<Button label="Send" icon="pi pi-send" class="ml-auto p-button-lg"></Button>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup>
|
|
22
|
+
import InputText from "primevue/inputtext"
|
|
23
|
+
import Checkbox from "primevue/checkbox"
|
|
24
|
+
import Button from "primevue/button"
|
|
25
|
+
import Divider from "primevue/divider"
|
|
26
|
+
import Textarea from "primevue/textarea"
|
|
27
|
+
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<style>
|
|
31
|
+
|
|
32
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function routes(config = {}) {
|
|
2
|
+
const { prefix = '/', route = (r) => r } = config
|
|
3
|
+
|
|
4
|
+
return [
|
|
5
|
+
|
|
6
|
+
route({ name: 'user:delete', path: prefix + 'delete',
|
|
7
|
+
component: () => import("./Delete.vue") }),
|
|
8
|
+
route({ name: 'user:deleteFinished', path: prefix + 'delete-finished',
|
|
9
|
+
component: () => import("./DeleteFinished.vue") }),
|
|
10
|
+
route({ name: 'user:deleteFeedbackSent', path: prefix + 'delete-feedback-sent',
|
|
11
|
+
component: () => import("./DeleteFeedbackSent.vue") }),
|
|
12
|
+
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default routes
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full lg:w-6 md:w-9">
|
|
3
|
+
<div class="surface-card p-4 shadow-2 border-round">
|
|
4
|
+
<div class="text-center mb-5">
|
|
5
|
+
<div class="text-900 text-3xl font-medium mb-3">
|
|
6
|
+
Identification
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="flex flex-wrap align-items-center" v-if="userData !== undefined">
|
|
11
|
+
<div class="relative" @click="openImageEditor">
|
|
12
|
+
<Image v-if="userData?.image" :image="userData.image" class="mr-2 border-circle profile-image"
|
|
13
|
+
domResize width="200" height="200" />
|
|
14
|
+
<img v-else :src="identiconUrl" class="mr-2 border-circle profile-image">
|
|
15
|
+
</div>
|
|
16
|
+
<command-form service="userIdentification" :action="updateMethod"
|
|
17
|
+
:initialValues="{ name: userData?.name }"
|
|
18
|
+
:parameters="{ image: userData?.image }" v-slot="{ data }"
|
|
19
|
+
keepOnDone @done="handleNameSaved"
|
|
20
|
+
class="ml-3 mb-3 flex flex-column">
|
|
21
|
+
<div class="p-field flex flex-column">
|
|
22
|
+
<InputText type="text" v-model="data.name"
|
|
23
|
+
:class="{ 'p-invalid': data.nameError }"
|
|
24
|
+
class="p-inputtext-lg" placeholder="Your name" />
|
|
25
|
+
<small id="currentPassword-help" class="p-error">{{ data.nameError }}</small>
|
|
26
|
+
</div>
|
|
27
|
+
<Button type="submit" label="Save name" class="mt-3" icon="pi pi-save" />
|
|
28
|
+
</command-form>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script setup>
|
|
36
|
+
import { FileInput } from "@live-change/upload-frontend"
|
|
37
|
+
import { ComponentDialog } from "@live-change/frontend-base"
|
|
38
|
+
import { ImageEditor, Image } from "@live-change/image-frontend"
|
|
39
|
+
import { useDialog } from 'primevue/usedialog'
|
|
40
|
+
import InputText from 'primevue/inputtext'
|
|
41
|
+
import Button from 'primevue/button'
|
|
42
|
+
const dialog = useDialog()
|
|
43
|
+
|
|
44
|
+
import { shallowRef, ref, inject, computed } from 'vue'
|
|
45
|
+
import { path, live, actions, api as useApi } from '@live-change/vue3-ssr'
|
|
46
|
+
|
|
47
|
+
import { useToast } from 'primevue/usetoast'
|
|
48
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
49
|
+
const confirm = useConfirm()
|
|
50
|
+
const toast = useToast()
|
|
51
|
+
|
|
52
|
+
const api = useApi()
|
|
53
|
+
const [ ownerType, owner ] = api.client.value.user
|
|
54
|
+
? ['user_User', api.client.value.user]
|
|
55
|
+
: ['session_Session', api.client.value.session]
|
|
56
|
+
|
|
57
|
+
const dataPromise = live(path().userIdentification.sessionOrUserOwnedIdentification({
|
|
58
|
+
sessionOrUserType: ownerType, sessionOrUser: owner
|
|
59
|
+
}))
|
|
60
|
+
|
|
61
|
+
const identiconUrl = `/api/identicon/jdenticon/${ownerType}:${owner}/28.svg`
|
|
62
|
+
|
|
63
|
+
const workingZone = inject('workingZone')
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
function openImageEditor() {
|
|
67
|
+
dialog.open(ComponentDialog, {
|
|
68
|
+
props: {
|
|
69
|
+
header: 'Image Editor',
|
|
70
|
+
style: {
|
|
71
|
+
width: '50vw',
|
|
72
|
+
},
|
|
73
|
+
breakpoints:{
|
|
74
|
+
'960px': '75vw',
|
|
75
|
+
'640px': '90vw'
|
|
76
|
+
},
|
|
77
|
+
modal: true,
|
|
78
|
+
contentClass: "p-0"
|
|
79
|
+
},
|
|
80
|
+
data: {
|
|
81
|
+
component: shallowRef(ImageEditor),
|
|
82
|
+
props: {
|
|
83
|
+
type: 'circle'
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
onClose: (options) => {
|
|
87
|
+
const data = options.data
|
|
88
|
+
console.log("EDITOR RESULT", data)
|
|
89
|
+
console.log("WZ", workingZone)
|
|
90
|
+
workingZone.addPromise('update user image', (async () => {
|
|
91
|
+
await api.command(['userIdentification', updateMethod.value], { image: data.value })
|
|
92
|
+
toast.add({ severity:'info', summary: 'User image saved', life: 1500 })
|
|
93
|
+
})())
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handleNameSaved() {
|
|
99
|
+
toast.add({ severity:'info', summary: 'User name saved', life: 1500 })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const [ userData ] = await Promise.all([ dataPromise ])
|
|
103
|
+
|
|
104
|
+
const updateMethod = computed(() => userData.value ? 'updateMyIdentification' : 'setMyIdentification')
|
|
105
|
+
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<style scoped>
|
|
109
|
+
.profile-image {
|
|
110
|
+
aspect-ratio: 1/1;
|
|
111
|
+
width: 200px;
|
|
112
|
+
max-width: 100%;
|
|
113
|
+
height: auto;
|
|
114
|
+
border: 1px solid gray;
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UserIdentification v-if="objectType == 'session_Session' || objectType == 'user_User'"
|
|
3
|
+
:ownerType="objectType" :owner="object"
|
|
4
|
+
:data="data" :inline="inline" />
|
|
5
|
+
<span v-else><strong>{{ objectType }}</strong>: {{ object }}</span>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup>
|
|
9
|
+
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
objectType: {
|
|
12
|
+
type: String,
|
|
13
|
+
required: true
|
|
14
|
+
},
|
|
15
|
+
object: {
|
|
16
|
+
type: String,
|
|
17
|
+
required: true
|
|
18
|
+
},
|
|
19
|
+
data: {
|
|
20
|
+
type: Object,
|
|
21
|
+
default: null
|
|
22
|
+
},
|
|
23
|
+
inline: {
|
|
24
|
+
type: Boolean,
|
|
25
|
+
default: false
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
import { toRefs } from "@vueuse/core"
|
|
30
|
+
const { objectType, object, data, inline } = toRefs(props)
|
|
31
|
+
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<style scoped>
|
|
35
|
+
|
|
36
|
+
</style>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span>
|
|
3
|
+
<router-link v-if="ownerType == 'user_User' && profileRouteExists"
|
|
4
|
+
:to="{ name: 'user:profile', params: { user: owner } }"
|
|
5
|
+
v-ripple
|
|
6
|
+
:class="inline ? inlineClass : blockClass">
|
|
7
|
+
<Image v-if="userData?.image" :image="userData.image" class="mr-2 border-circle"
|
|
8
|
+
:style="imageStyle"/>
|
|
9
|
+
<img v-else :src="identiconUrl" class="mr-2 border-circle" :style="imageStyle" domResize />
|
|
10
|
+
<span class="text-overflow-ellipsis white-space-nowrap overflow-hidden"
|
|
11
|
+
:class="[ ownerType == 'user_User' ? 'font-medium' : 'font-italic' ]">
|
|
12
|
+
{{ name }}
|
|
13
|
+
</span>
|
|
14
|
+
</router-link>
|
|
15
|
+
<span v-else-if="ownerType == 'email_Email'">
|
|
16
|
+
<i class="pi pi-envelope mr-2 ml-1"
|
|
17
|
+
style="font-size: 1.3rem; margin-right: 0.7rem !important; position: relative; top: 3px;" />
|
|
18
|
+
<span class="text-overflow-ellipsis white-space-nowrap overflow-hidden">
|
|
19
|
+
{{ owner }}
|
|
20
|
+
</span>
|
|
21
|
+
</span>
|
|
22
|
+
<span v-else :class="inline ? inlineClass : blockClass">
|
|
23
|
+
<Image v-if="userData?.image" :image="userData.image" class="mr-2 border-circle"
|
|
24
|
+
:style="imageStyle"/>
|
|
25
|
+
<img v-else :src="identiconUrl" class="mr-2 border-circle" :style="imageStyle" domResize />
|
|
26
|
+
<span class="text-overflow-ellipsis white-space-nowrap overflow-hidden"
|
|
27
|
+
:class="[ ownerType == 'user_User' ? 'font-medium' : 'font-italic' ]">
|
|
28
|
+
{{ name }}
|
|
29
|
+
</span>
|
|
30
|
+
</span>
|
|
31
|
+
</span>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup>
|
|
35
|
+
|
|
36
|
+
import { Image } from "@live-change/image-frontend"
|
|
37
|
+
import { colors, animals, uniqueNamesGenerator } from "unique-names-generator"
|
|
38
|
+
|
|
39
|
+
const props = defineProps({
|
|
40
|
+
ownerType: {
|
|
41
|
+
type: String,
|
|
42
|
+
required: true
|
|
43
|
+
},
|
|
44
|
+
owner: {
|
|
45
|
+
type: String,
|
|
46
|
+
required: true
|
|
47
|
+
},
|
|
48
|
+
data: {
|
|
49
|
+
type: Object,
|
|
50
|
+
default: null
|
|
51
|
+
},
|
|
52
|
+
inline: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
default: false
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const inlineClass = ""
|
|
59
|
+
const blockClass = "flex align-items-center cursor-pointer text-700 hover:surface-100 border-round p-ripple"
|
|
60
|
+
const inlineImageSize = '1em'
|
|
61
|
+
const blockImageSize = '28px'
|
|
62
|
+
|
|
63
|
+
import { toRefs } from "@vueuse/core"
|
|
64
|
+
const { data, inline } = toRefs(props)
|
|
65
|
+
const { ownerType, owner } = props
|
|
66
|
+
|
|
67
|
+
import { useRouter } from 'vue-router'
|
|
68
|
+
const router = useRouter()
|
|
69
|
+
const profileRouteExists = router.hasRoute('user:profile')
|
|
70
|
+
|
|
71
|
+
import { path, live, actions } from '@live-change/vue3-ssr'
|
|
72
|
+
|
|
73
|
+
const dataPromise = data !== undefined ? Promise.resolve(data)
|
|
74
|
+
: live(path().userIdentification.sessionOrUserOwnedIdentification({
|
|
75
|
+
sessionOrUserType: ownerType, sessionOrUser: owner
|
|
76
|
+
}))
|
|
77
|
+
|
|
78
|
+
const identiconUrl = `/api/identicon/jdenticon/${ownerType}:${owner}/28.svg`
|
|
79
|
+
|
|
80
|
+
import { computed } from 'vue'
|
|
81
|
+
|
|
82
|
+
const imageStyle = computed(() => inline
|
|
83
|
+
? { width: inlineImageSize, height: inlineImageSize }
|
|
84
|
+
: { width: blockImageSize, height: blockImageSize }
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
const [ userData ] = await Promise.all([ dataPromise ])
|
|
88
|
+
|
|
89
|
+
const nameGeneratorConfig = {
|
|
90
|
+
dictionaries: [/*["anonymous", "unnamed"],*/ colors, animals],
|
|
91
|
+
separator: ' ',
|
|
92
|
+
seed: ownerType + '_' + owner
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const name = computed(() => userData.value?.name || uniqueNamesGenerator(nameGeneratorConfig))
|
|
96
|
+
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<style scoped>
|
|
100
|
+
|
|
101
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function routes(config = {}) {
|
|
2
|
+
const { prefix = '/', route = (r) => r } = config
|
|
3
|
+
|
|
4
|
+
return [
|
|
5
|
+
|
|
6
|
+
route({ name: 'user:identification', path: prefix + 'identification',
|
|
7
|
+
component: () => import("./IdentificationSettings.vue") })
|
|
8
|
+
|
|
9
|
+
]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default routes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<pre data-headers>{{ JSON.stringify(metadata, null, ' ') }}</pre>
|
|
3
|
+
<div data-html class="message m-6">
|
|
4
|
+
<p class="text-lg">
|
|
5
|
+
Hello!
|
|
6
|
+
</p>
|
|
7
|
+
<p>
|
|
8
|
+
You are trying to connect this email address to your DEMO account.
|
|
9
|
+
In order to confirm that, please enter secret code:
|
|
10
|
+
</p>
|
|
11
|
+
<p class="text-3xl font-medium">{{ code }}</p>
|
|
12
|
+
<p>
|
|
13
|
+
Or click the button below:
|
|
14
|
+
</p>
|
|
15
|
+
<div>
|
|
16
|
+
<a :href="linkAddress" class="no-underline">
|
|
17
|
+
<Button label="Confirm email" class="p-button-lg" />
|
|
18
|
+
</a>
|
|
19
|
+
</div>
|
|
20
|
+
<p>
|
|
21
|
+
Or copy this address to your browser address bar:<br>
|
|
22
|
+
<a :href="linkAddress">
|
|
23
|
+
{{ linkAddress }}
|
|
24
|
+
</a>
|
|
25
|
+
</p>
|
|
26
|
+
<p>
|
|
27
|
+
Let us know in case it's not you.
|
|
28
|
+
</p>
|
|
29
|
+
<p>
|
|
30
|
+
See you soon<br>
|
|
31
|
+
Live Change Team
|
|
32
|
+
</p>
|
|
33
|
+
<img src="/images/logo128.png">
|
|
34
|
+
</div>
|
|
35
|
+
<pre class="message" data-text>
|
|
36
|
+
Hello!
|
|
37
|
+
|
|
38
|
+
You are trying to connect this email address to your DEMO account.
|
|
39
|
+
In order to confirm that, please enter secret code:
|
|
40
|
+
{{ code }}
|
|
41
|
+
|
|
42
|
+
Or please click link below or copy address to your browser address bar:
|
|
43
|
+
|
|
44
|
+
{{ linkAddress }}
|
|
45
|
+
|
|
46
|
+
Let us know in case it's not you.
|
|
47
|
+
|
|
48
|
+
See you soon
|
|
49
|
+
Live Change Team
|
|
50
|
+
</pre>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<script setup>
|
|
54
|
+
import Button from "primevue/button"
|
|
55
|
+
|
|
56
|
+
const { action, contact, json } = defineProps({
|
|
57
|
+
action: {
|
|
58
|
+
type: String,
|
|
59
|
+
required: true
|
|
60
|
+
},
|
|
61
|
+
contact: {
|
|
62
|
+
type: String,
|
|
63
|
+
required: true
|
|
64
|
+
},
|
|
65
|
+
json: {
|
|
66
|
+
type: String,
|
|
67
|
+
required: true
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const data = JSON.parse(json)
|
|
72
|
+
const secrets = data.secrets
|
|
73
|
+
|
|
74
|
+
const secretLink = secrets.find(secret => secret.type == 'link')
|
|
75
|
+
|
|
76
|
+
const secretCode = secrets.find(secret => secret.type == 'code')
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
const metadata = {
|
|
80
|
+
from: 'admin@flipchart.live',
|
|
81
|
+
subject: 'Confirm your email address.',
|
|
82
|
+
to: contact
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const linkAddress = ENV_BASE_HREF + '/link/' + secretLink.secret.secretCode
|
|
86
|
+
|
|
87
|
+
const code = secretCode.secret.secretCode
|
|
88
|
+
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<style scoped>
|
|
92
|
+
img {
|
|
93
|
+
width: 100%;
|
|
94
|
+
max-width: 100px;
|
|
95
|
+
}
|
|
96
|
+
.message {
|
|
97
|
+
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
|
|
98
|
+
color: #495057;
|
|
99
|
+
font-weight: 400;
|
|
100
|
+
}
|
|
101
|
+
pre {
|
|
102
|
+
border-top: 1px solid black;
|
|
103
|
+
border-bottom: 1px solid black;
|
|
104
|
+
}
|
|
105
|
+
</style>
|