@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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/e2e/codecept.conf.js +60 -0
  3. package/e2e/connectEmailCode.test.js +61 -0
  4. package/e2e/connectEmailLink.test.js +60 -0
  5. package/e2e/delete.test.js +44 -0
  6. package/e2e/disconnectEmail.test.js +42 -0
  7. package/e2e/resetPasswordWithEmailCode.test.js +62 -0
  8. package/e2e/resetPasswordWithEmailLink.test.js +62 -0
  9. package/e2e/setPassword.test.js +70 -0
  10. package/e2e/signInEmailCode.test.js +52 -0
  11. package/e2e/signInEmailLink.test.js +52 -0
  12. package/e2e/signInEmailPassword.test.js +47 -0
  13. package/e2e/signOut.test.js +41 -0
  14. package/e2e/signUpEmailCode.test.js +41 -0
  15. package/e2e/signUpEmailLink.test.js +41 -0
  16. package/e2e/steps.d.ts +12 -0
  17. package/e2e/steps_file.js +89 -0
  18. package/front/index.html +11 -0
  19. package/front/public/favicon.ico +0 -0
  20. package/front/public/images/empty-photo.svg +38 -0
  21. package/front/public/images/empty-user-photo.svg +33 -0
  22. package/front/public/images/logo.svg +34 -0
  23. package/front/public/images/logo128.png +0 -0
  24. package/front/src/App.vue +31 -0
  25. package/front/src/Index.vue +14 -0
  26. package/front/src/NavBar.vue +103 -0
  27. package/front/src/SettingsTabs.vue +48 -0
  28. package/front/src/connected/Connect.vue +58 -0
  29. package/front/src/connected/ConnectFinished.vue +16 -0
  30. package/front/src/connected/Connected.vue +84 -0
  31. package/front/src/connected/routes.js +16 -0
  32. package/front/src/delete/Delete.vue +53 -0
  33. package/front/src/delete/DeleteFeedbackSent.vue +16 -0
  34. package/front/src/delete/DeleteFinished.vue +32 -0
  35. package/front/src/delete/routes.js +16 -0
  36. package/front/src/entry-client.js +6 -0
  37. package/front/src/entry-server.js +6 -0
  38. package/front/src/identification/IdentificationSettings.vue +116 -0
  39. package/front/src/identification/ObjectIdentification.vue +36 -0
  40. package/front/src/identification/UserIdentification.vue +101 -0
  41. package/front/src/identification/routes.js +12 -0
  42. package/front/src/message-auth/ConnectEmail.vue +105 -0
  43. package/front/src/message-auth/MessageLink.vue +95 -0
  44. package/front/src/message-auth/MessageSent.vue +103 -0
  45. package/front/src/message-auth/ResetPasswordEmail.vue +105 -0
  46. package/front/src/message-auth/SignInEmail.vue +105 -0
  47. package/front/src/message-auth/SignUpEmail.vue +105 -0
  48. package/front/src/message-auth/routes.js +25 -0
  49. package/front/src/notifications/NotificationButtons.vue +70 -0
  50. package/front/src/notifications/NotificationListPage.vue +22 -0
  51. package/front/src/notifications/NotificationsIcon.vue +75 -0
  52. package/front/src/notifications/NotificationsList.vue +144 -0
  53. package/front/src/notifications/NotificationsSettings.vue +117 -0
  54. package/front/src/notifications/SimpleNotification.vue +34 -0
  55. package/front/src/notifications/TestNotification.vue +25 -0
  56. package/front/src/notifications/UnknownNotification.vue +25 -0
  57. package/front/src/notifications/notificationTypes.js +11 -0
  58. package/front/src/notifications/routes.js +37 -0
  59. package/front/src/password/ChangePassword.vue +106 -0
  60. package/front/src/password/ChangePasswordFinished.vue +16 -0
  61. package/front/src/password/ResetPassword.vue +56 -0
  62. package/front/src/password/ResetPasswordFinished.vue +16 -0
  63. package/front/src/password/ResetPasswordForm.vue +118 -0
  64. package/front/src/password/routes.js +41 -0
  65. package/front/src/router.js +90 -0
  66. package/front/src/settings/Settings.vue +33 -0
  67. package/front/src/settings/SettingsIndex.vue +22 -0
  68. package/front/src/settings/SettingsMenu.vue +81 -0
  69. package/front/src/settings/SettingsMenuItem.vue +35 -0
  70. package/front/src/sign/SignIn.vue +93 -0
  71. package/front/src/sign/SignInFinished.vue +27 -0
  72. package/front/src/sign/SignOut.vue +37 -0
  73. package/front/src/sign/SignOutFinished.vue +16 -0
  74. package/front/src/sign/SignUp.vue +51 -0
  75. package/front/src/sign/SignUpFinished.vue +16 -0
  76. package/front/src/sign/routes.js +24 -0
  77. package/front/vite.config.js +11 -0
  78. package/index.js +11 -0
  79. package/package.json +87 -0
  80. package/server/init.js +53 -0
  81. package/server/security.config.js +53 -0
  82. 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,6 @@
1
+ import { clientEntry } from '@live-change/frontend-base/client-entry.js'
2
+ import App from './App.vue'
3
+ import { createRouter } from './router'
4
+
5
+
6
+ clientEntry(App, createRouter)
@@ -0,0 +1,6 @@
1
+ import { serverEntry } from '@live-change/frontend-base/server-entry.js'
2
+ import App from './App.vue'
3
+ import { createRouter } from './router'
4
+
5
+ const render = serverEntry(App, createRouter)
6
+ export { render }
@@ -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>