@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,75 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<a v-if="unreadNotificationsCount"
|
|
3
|
+
v-ripple
|
|
4
|
+
@click="showNotifications"
|
|
5
|
+
class="flex mx-2 px-3 p-3 py-3 align-items-center text-600 hover:text-900 hover:surface-100
|
|
6
|
+
font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
7
|
+
<i class="pi pi-bell text-base text-2xl p-overlay-badge">
|
|
8
|
+
<Badge v-if="unreadNotificationsCount?.count" :value="unreadNotificationsCount?.count ?? 0"></Badge>
|
|
9
|
+
</i>
|
|
10
|
+
</a>
|
|
11
|
+
<OverlayPanel v-if="isMounted" ref="overlayPanel" class="notifications-panel">
|
|
12
|
+
<loading-zone suspense>
|
|
13
|
+
<template v-slot:loading>
|
|
14
|
+
<div class="flex align-items-center justify-content-center top-0 left-0 notifications-loading">
|
|
15
|
+
<ProgressSpinner animationDuration=".5s"/>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
<template v-slot:default="{ isLoading }">
|
|
19
|
+
<working-zone>
|
|
20
|
+
<template v-slot:working>
|
|
21
|
+
<div class="fixed w-full h-full flex align-items-center justify-content-center top-0 left-0">
|
|
22
|
+
<ProgressSpinner animationDuration=".5s"/>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
<template v-slot:default="{ isWorking }">
|
|
26
|
+
<div :style="(isWorking || isLoading) ? 'filter: blur(4px)' : ''" class="working-blur">
|
|
27
|
+
<NotificationsList />
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
</working-zone>
|
|
31
|
+
</template>
|
|
32
|
+
</loading-zone>
|
|
33
|
+
</OverlayPanel>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup>
|
|
37
|
+
|
|
38
|
+
import Badge from "primevue/badge"
|
|
39
|
+
import OverlayPanel from 'primevue/overlaypanel'
|
|
40
|
+
import ProgressSpinner from "primevue/progressspinner"
|
|
41
|
+
|
|
42
|
+
import NotificationsList from "./NotificationsList.vue"
|
|
43
|
+
|
|
44
|
+
import { ref, onMounted } from 'vue'
|
|
45
|
+
|
|
46
|
+
const overlayPanel = ref()
|
|
47
|
+
|
|
48
|
+
const isMounted = ref(false)
|
|
49
|
+
onMounted(() => isMounted.value = true)
|
|
50
|
+
|
|
51
|
+
function showNotifications(event) {
|
|
52
|
+
overlayPanel.value.toggle(event)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
import { live, path } from '@live-change/vue3-ssr'
|
|
56
|
+
|
|
57
|
+
const unreadNotificationsCount = await live(path().notification.myUnreadCount({ }))
|
|
58
|
+
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<style>
|
|
62
|
+
.notifications-panel .p-overlaypanel-content {
|
|
63
|
+
padding: 0px;
|
|
64
|
+
max-height: calc(90vh - 50px);
|
|
65
|
+
overflow-y: auto;
|
|
66
|
+
}
|
|
67
|
+
.notifications-panel {
|
|
68
|
+
width: 500px;
|
|
69
|
+
max-width: 80%;
|
|
70
|
+
}
|
|
71
|
+
.notifications-loading {
|
|
72
|
+
height: 300px;
|
|
73
|
+
max-height: 80%;
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex align-items-center justify-content-between mb-1 px-3 pt-1">
|
|
3
|
+
<div class="text-900 font-medium text-xl">Notifications</div>
|
|
4
|
+
<div>
|
|
5
|
+
<Button @click="$refs.menu.toggle($event)"
|
|
6
|
+
icon="pi pi-ellipsis-v" class="p-button-text p-button-plain p-button-rounded" />
|
|
7
|
+
<Menu ref="menu" :popup="true" :model="menuItems"></Menu>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
<ul class="list-none p-0 m-0 notifications">
|
|
11
|
+
<div v-for="(bucket, bucketIndex) in notificationsBuckets.buckets" :key="bucket.id"
|
|
12
|
+
:style="{ backgroundz: `hsl(${bucket.id * 11}, 100%, 80%)` }">
|
|
13
|
+
<div v-for="(notification, index) in bucket.data" :key="notification.id" :ref="el => bucket.domElements[index] = el"
|
|
14
|
+
class="notification border-bottom-1 surface-border"
|
|
15
|
+
:class="{ selected: selectedNotification == notification.to }">
|
|
16
|
+
<component :is="notificationComponent(notification)" :notification="notification" />
|
|
17
|
+
<Button @click="() => selectNotification(notification)"
|
|
18
|
+
icon="pi pi-ellipsis-h" class="p-button-rounded p-button-text notification-more-button" />
|
|
19
|
+
<NotificationButtons :notification="notification" />
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<scroll-border placement="bottom"
|
|
23
|
+
:load="notificationsBuckets.loadBottom"
|
|
24
|
+
:canLoad="notificationsBuckets.canLoadBottom" />
|
|
25
|
+
</ul>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
import Button from "primevue/button"
|
|
30
|
+
import Menu from "primevue/menu"
|
|
31
|
+
|
|
32
|
+
import ScrollBorder from 'vue3-scroll-border'
|
|
33
|
+
|
|
34
|
+
import { useToast } from 'primevue/usetoast'
|
|
35
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
36
|
+
const confirm = useConfirm()
|
|
37
|
+
const toast = useToast()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
import { ref, inject } from 'vue'
|
|
41
|
+
|
|
42
|
+
const workingZone = inject('workingZone')
|
|
43
|
+
|
|
44
|
+
import {notificationTypes} from "./notificationTypes.js"
|
|
45
|
+
|
|
46
|
+
import NotificationButtons from "./NotificationButtons.vue"
|
|
47
|
+
|
|
48
|
+
import { path, live, actions, api, rangeBuckets, reverseRange } from '@live-change/vue3-ssr'
|
|
49
|
+
|
|
50
|
+
const notificationApi = actions().notification
|
|
51
|
+
|
|
52
|
+
function notificationComponent(notification) {
|
|
53
|
+
const known = notificationTypes[notification.notificationType]
|
|
54
|
+
if(known) return known.component
|
|
55
|
+
return notificationTypes.unknown.component
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const selectedNotification = ref(null)
|
|
59
|
+
function selectNotification(notification) {
|
|
60
|
+
console.log("SELECT NOTIFICATION", notification)
|
|
61
|
+
selectedNotification.value = notification.to
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const menuItems = [
|
|
65
|
+
{
|
|
66
|
+
label: 'Mark all as read',
|
|
67
|
+
icon: 'pi pi-check',
|
|
68
|
+
command: () => {
|
|
69
|
+
workingZone.addPromise('markNotification', (async () => {
|
|
70
|
+
await notificationApi.markAllAsRead({ })
|
|
71
|
+
toast.add({
|
|
72
|
+
severity: 'success', summary:' Notifications read',
|
|
73
|
+
detail:'all notifications have been marked as read', life: 3000
|
|
74
|
+
})
|
|
75
|
+
})())
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: 'Delete all',
|
|
80
|
+
icon: 'pi pi-times',
|
|
81
|
+
command: () => {
|
|
82
|
+
workingZone.addPromise('markNotification', (async () => {
|
|
83
|
+
await notificationApi.deleteAll({ })
|
|
84
|
+
toast.add({
|
|
85
|
+
severity: 'warn', summary: 'Notifications deleted',
|
|
86
|
+
detail: 'All notifications have been deleted', life: 3000
|
|
87
|
+
})
|
|
88
|
+
})())
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
const wait = new Promise(r => setTimeout(r, 100))
|
|
94
|
+
|
|
95
|
+
const [ notificationsBuckets ] = await Promise.all([
|
|
96
|
+
rangeBuckets(
|
|
97
|
+
(range, p) => p.notification.myNotifications(reverseRange(range)),
|
|
98
|
+
{ bucketSize: 10 }
|
|
99
|
+
)
|
|
100
|
+
])
|
|
101
|
+
|
|
102
|
+
await wait
|
|
103
|
+
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<style lang="scss">
|
|
107
|
+
.notifications {
|
|
108
|
+
.notification:last-child {
|
|
109
|
+
border-bottom: none;
|
|
110
|
+
}
|
|
111
|
+
.notification {
|
|
112
|
+
position: relative;
|
|
113
|
+
.notification-buttons {
|
|
114
|
+
visibility: hidden;
|
|
115
|
+
position: absolute;
|
|
116
|
+
right: 40px;
|
|
117
|
+
top: 5px;
|
|
118
|
+
//transform: translate(0, -50%);
|
|
119
|
+
}
|
|
120
|
+
.notification-more-button {
|
|
121
|
+
position: absolute;
|
|
122
|
+
right: 5px;
|
|
123
|
+
top: 2%;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
.notification.selected {
|
|
127
|
+
.notification-buttons {
|
|
128
|
+
visibility: visible;
|
|
129
|
+
}
|
|
130
|
+
.notification-more-button {
|
|
131
|
+
visibility: hidden;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
.notification:hover {
|
|
135
|
+
.notification-buttons {
|
|
136
|
+
visibility: visible;
|
|
137
|
+
}
|
|
138
|
+
.notification-more-button {
|
|
139
|
+
visibility: hidden;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
</style>
|
|
@@ -0,0 +1,117 @@
|
|
|
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>
|
|
5
|
+
<h1>Notifications settings</h1>
|
|
6
|
+
</div>
|
|
7
|
+
<div v-for="notificationType in settings">
|
|
8
|
+
<div>
|
|
9
|
+
<h2>{{ notificationType.type }}</h2>
|
|
10
|
+
</div>
|
|
11
|
+
<div>
|
|
12
|
+
<div v-for="contact in notificationType.contacts"
|
|
13
|
+
class="flex flex-row align-items-center mb-3">
|
|
14
|
+
<div class="flex-grow-1 md:mb-2">
|
|
15
|
+
<i class="pi" :class="contactTypesIcons[contact.contactType]"></i>
|
|
16
|
+
<span class="ml-2">{{ contactText(contact.contact, contact.contactType) }}</span>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="">
|
|
19
|
+
<InputSwitch v-model="contact.setting.value.active"/>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
|
|
30
|
+
import InputSwitch from 'primevue/inputswitch'
|
|
31
|
+
|
|
32
|
+
import pluralize from 'pluralize'
|
|
33
|
+
import { synchronized } from "@live-change/vue3-components"
|
|
34
|
+
import { computed, ref } from 'vue'
|
|
35
|
+
import { useToast } from 'primevue/usetoast'
|
|
36
|
+
const toast = useToast()
|
|
37
|
+
|
|
38
|
+
const checked = ref()
|
|
39
|
+
|
|
40
|
+
import { api as useApi, live, path, actions } from '@live-change/vue3-ssr'
|
|
41
|
+
const api = useApi()
|
|
42
|
+
|
|
43
|
+
const clientConfig = api.getServiceDefinition('notification')?.clientConfig
|
|
44
|
+
|
|
45
|
+
const notificationApi = actions().notification
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
const contactTypesIcons = {
|
|
49
|
+
email: 'pi-at',
|
|
50
|
+
web: 'pi-globe',
|
|
51
|
+
phone: 'pi-phone'
|
|
52
|
+
}
|
|
53
|
+
function contactText(contact, type) {
|
|
54
|
+
if(type == 'web') return 'Web'
|
|
55
|
+
return contact
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const allContacts = await Promise.all(clientConfig.contactTypes.map(async contactType => {
|
|
59
|
+
const contactTypeUpper = contactType[0].toUpperCase() + contactType.slice(1)
|
|
60
|
+
const contactTypeLong = contactType + '_' + contactTypeUpper
|
|
61
|
+
let p = path()[contactType]['myUser' + pluralize(contactTypeUpper)]({})
|
|
62
|
+
p = p.with(contact =>
|
|
63
|
+
path().notification.contactOwnedNotificationSettings({
|
|
64
|
+
contactType: contactTypeLong, contact: contact[contactType]
|
|
65
|
+
}).bind('settings')
|
|
66
|
+
)
|
|
67
|
+
/*for(const notificationType of clientConfig.notificationTypes) {
|
|
68
|
+
p = p.with(contact => path().notification.contactAndNotificationOwnedNotificationSetting({
|
|
69
|
+
contactType, contact: contact[contactType], notificationType, notification: ''
|
|
70
|
+
}).bind(notificationType))
|
|
71
|
+
}*/
|
|
72
|
+
return {
|
|
73
|
+
type: contactType,
|
|
74
|
+
contactType,
|
|
75
|
+
contactTypeUpper,
|
|
76
|
+
contactTypeLong,
|
|
77
|
+
list: await live(p)
|
|
78
|
+
}
|
|
79
|
+
}))
|
|
80
|
+
|
|
81
|
+
const contacts = computed(() => {
|
|
82
|
+
const obj = {}
|
|
83
|
+
for(const type of allContacts) {
|
|
84
|
+
obj[type.type] = type.list.value
|
|
85
|
+
}
|
|
86
|
+
return obj
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const settings = computed(() => clientConfig.notificationTypes.map(notificationType => {
|
|
90
|
+
const contacts = allContacts.map(contactsData => contactsData.list.value.map(contact => {
|
|
91
|
+
const contactType = contactsData.type
|
|
92
|
+
const settingSource = computed(() => contact.settings.find(s => s.notificationType == notificationType))
|
|
93
|
+
const setting = synchronized({
|
|
94
|
+
source: settingSource,
|
|
95
|
+
update: notificationApi.setOrUpdateContactAndNotificationOwnedNotificationSetting,
|
|
96
|
+
identifiers: {
|
|
97
|
+
contact: contact[contactType], contactType: contactsData.contactTypeLong,
|
|
98
|
+
notificationType, notification: notificationType
|
|
99
|
+
},
|
|
100
|
+
recursive: true,
|
|
101
|
+
onSave: () => toast.add({ severity: 'info', summary: 'Notification settings saved', life: 1500 })
|
|
102
|
+
}).value
|
|
103
|
+
return {
|
|
104
|
+
contactType,
|
|
105
|
+
contact: contact[contactType],
|
|
106
|
+
settingSource,
|
|
107
|
+
setting
|
|
108
|
+
}
|
|
109
|
+
})).flat()
|
|
110
|
+
return {
|
|
111
|
+
type: notificationType,
|
|
112
|
+
contacts
|
|
113
|
+
}
|
|
114
|
+
}))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
</script>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<li class="px-3 py-2 flex align-items-start
|
|
3
|
+
justify-content-between flex-column hover:surface-100">
|
|
4
|
+
<slot></slot>
|
|
5
|
+
<span class="block text-500 font-medium mt-2">
|
|
6
|
+
{{ DateTime.fromISO(notification.time).toRelative({ base: DateTime.fromMillis(now) }) }}
|
|
7
|
+
</span>
|
|
8
|
+
</li>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup>
|
|
12
|
+
|
|
13
|
+
const props = defineProps({
|
|
14
|
+
notification: {
|
|
15
|
+
type: Object,
|
|
16
|
+
required: true
|
|
17
|
+
},
|
|
18
|
+
image: {
|
|
19
|
+
type: String
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
import { useTimestamp, toRefs } from '@vueuse/core'
|
|
24
|
+
|
|
25
|
+
const { notification, image } = toRefs(props)
|
|
26
|
+
|
|
27
|
+
const now = useTimestamp({ interval: 1000 })
|
|
28
|
+
import { DateTime } from 'luxon'
|
|
29
|
+
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<style scoped>
|
|
33
|
+
|
|
34
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SimpleNotification :notification="notification">
|
|
3
|
+
Test Notification
|
|
4
|
+
</SimpleNotification>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
|
|
9
|
+
import SimpleNotification from "./SimpleNotification.vue"
|
|
10
|
+
import { toRefs } from "@vueuse/core"
|
|
11
|
+
|
|
12
|
+
const props = defineProps({
|
|
13
|
+
notification: {
|
|
14
|
+
type: Object,
|
|
15
|
+
required: true
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const { notification } = toRefs(props)
|
|
20
|
+
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<style scoped>
|
|
24
|
+
|
|
25
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SimpleNotification :notification="notification">
|
|
3
|
+
<div class="text-orange-600 text-2xl">
|
|
4
|
+
Unknown notification!
|
|
5
|
+
</div>
|
|
6
|
+
<pre class="w-full overflow-hidden">{{ notification }}</pre>
|
|
7
|
+
</SimpleNotification>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup>
|
|
11
|
+
|
|
12
|
+
import SimpleNotification from "./SimpleNotification.vue"
|
|
13
|
+
|
|
14
|
+
const { notification } = defineProps({
|
|
15
|
+
notification: {
|
|
16
|
+
type: Object,
|
|
17
|
+
required: true
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<style scoped>
|
|
24
|
+
|
|
25
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import UnknownNotification from "./UnknownNotification.vue"
|
|
2
|
+
import TestNotification from "./TestNotification.vue"
|
|
3
|
+
|
|
4
|
+
export const notificationTypes = {
|
|
5
|
+
unknown: {
|
|
6
|
+
component: UnknownNotification
|
|
7
|
+
},
|
|
8
|
+
example_TestNotification: {
|
|
9
|
+
component: TestNotification
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export function notificationsSettingsRoutes(config = {}) {
|
|
4
|
+
const { prefix = '/', route = (r) => r } = config
|
|
5
|
+
|
|
6
|
+
return [
|
|
7
|
+
|
|
8
|
+
route({ name: 'user:notificationsSettings', path: prefix + 'notifications-settings', props: true,
|
|
9
|
+
component: () => import("./NotificationsSettings.vue"), meta: { signedIn: true } }),
|
|
10
|
+
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export function notificationsRoutes(config = {}) {
|
|
16
|
+
const { prefix = '/', route = (r) => r } = config
|
|
17
|
+
|
|
18
|
+
return [
|
|
19
|
+
|
|
20
|
+
route({ name: 'user:notificationsList', path: prefix + 'notifications', props: true,
|
|
21
|
+
component: () => import("./NotificationListPage.vue"), meta: { signedIn: true } }),
|
|
22
|
+
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function routes(config = {}) {
|
|
27
|
+
const { prefix = '/', route = (r) => r } = config
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
|
|
31
|
+
...notificationsRoutes(config),
|
|
32
|
+
...notificationsRoutes(config)
|
|
33
|
+
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default routes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full lg:w-6 md:w-9">
|
|
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">
|
|
7
|
+
{{ passwordExists ? 'Change password' : 'Set password' }}
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<command-form service="passwordAuthentication"
|
|
12
|
+
:action="passwordExists ? 'changePassword' : 'setPassword'"
|
|
13
|
+
v-slot="{ data }" ref="form" @done="handleDone">
|
|
14
|
+
|
|
15
|
+
<template v-if="isMounted">
|
|
16
|
+
|
|
17
|
+
<div class="p-field mb-3" v-if="passwordExists">
|
|
18
|
+
<label for="currentPassword" class="block text-900 font-medium mb-2">Current password</label>
|
|
19
|
+
<Password id="currentPassword" class="w-full" inputClass="w-full" toggleMask
|
|
20
|
+
:class="{ 'p-invalid': data.currentPasswordHashError }"
|
|
21
|
+
v-model="data.currentPasswordHash" />
|
|
22
|
+
<small id="currentPassword-help" class="p-error">{{ data.currentPasswordHashError }}</small>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="p-field mb-3">
|
|
26
|
+
<label for="newPassword" class="block text-900 font-medium mb-2">New password</label>
|
|
27
|
+
<Password id="newPassword" class="w-full" inputClass="w-full" toggleMask
|
|
28
|
+
:class="{ 'p-invalid': data.passwordHashError }"
|
|
29
|
+
v-model="data.passwordHash">
|
|
30
|
+
<template #footer>
|
|
31
|
+
<Divider />
|
|
32
|
+
<p class="p-mt-2">Suggestions</p>
|
|
33
|
+
<ul class="p-pl-2 p-ml-2 p-mt-0" style="line-height: 1.5">
|
|
34
|
+
<li>At least one lowercase</li>
|
|
35
|
+
<li>At least one uppercase</li>
|
|
36
|
+
<li>At least one numeric</li>
|
|
37
|
+
<li>Minimum 8 characters</li>
|
|
38
|
+
</ul>
|
|
39
|
+
</template>
|
|
40
|
+
</Password>
|
|
41
|
+
<small id="newPassword-help" class="p-error">{{ data.passwordHashError }}</small>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="p-field mb-3">
|
|
45
|
+
<label for="reenterPassword" class="block text-900 font-medium mb-2">Re-enter password</label>
|
|
46
|
+
<Password id="reenterPassword" class="w-full" inputClass="w-full"
|
|
47
|
+
v-model="secondPassword"
|
|
48
|
+
:feedback="false" toggleMask />
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<Button :label="passwordExists ? 'Change password' : 'Set password'"
|
|
54
|
+
type="submit"
|
|
55
|
+
icon="pi pi-key" class="w-full"></Button>
|
|
56
|
+
|
|
57
|
+
</command-form>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<script setup>
|
|
64
|
+
|
|
65
|
+
import InputText from "primevue/inputtext"
|
|
66
|
+
import Checkbox from "primevue/checkbox"
|
|
67
|
+
import Button from "primevue/button"
|
|
68
|
+
import Divider from "primevue/divider"
|
|
69
|
+
import Password from "primevue/password"
|
|
70
|
+
import SettingsTabs from "../SettingsTabs.vue"
|
|
71
|
+
|
|
72
|
+
import { live, path } from '@live-change/vue3-ssr'
|
|
73
|
+
import { computed, ref, onMounted } from 'vue'
|
|
74
|
+
|
|
75
|
+
import { useRouter } from 'vue-router'
|
|
76
|
+
const router = useRouter()
|
|
77
|
+
|
|
78
|
+
const isMounted = ref(false)
|
|
79
|
+
onMounted(() => isMounted.value = true)
|
|
80
|
+
|
|
81
|
+
const secondPassword = ref('')
|
|
82
|
+
const form = ref()
|
|
83
|
+
|
|
84
|
+
onMounted(() => {
|
|
85
|
+
form.value.addValidator('passwordHash', () => {
|
|
86
|
+
const value = form.value.getFieldValue('passwordHash')
|
|
87
|
+
console.log("PASSWORDS MATCH?", secondPassword.value, value)
|
|
88
|
+
if(value != secondPassword.value) return "passwordsNotMatch"
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
const passwordExists = await live(path().passwordAuthentication.myUserPasswordAuthenticationExists())
|
|
94
|
+
|
|
95
|
+
function handleDone({ parameters, result }) {
|
|
96
|
+
console.log("FORM DONE", parameters, result)
|
|
97
|
+
router.push({
|
|
98
|
+
name: 'user:changePasswordFinished',
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<style>
|
|
105
|
+
|
|
106
|
+
</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">Password changed</div>
|
|
5
|
+
<p class="mt-0 p-0 line-height-3">You have successfully set your password.</p>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup>
|
|
11
|
+
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<style>
|
|
15
|
+
|
|
16
|
+
</style>
|
|
@@ -0,0 +1,56 @@
|
|
|
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">Reset password</div>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<command-form service="passwordAuthentication" action="resetPasswordEmail" v-slot="{ data }"
|
|
9
|
+
@done="handleDone" keepOnDone v-if="isMounted">
|
|
10
|
+
|
|
11
|
+
<div class="p-field mb-3">
|
|
12
|
+
<label for="email" class="block text-900 font-medium mb-2">
|
|
13
|
+
Email address
|
|
14
|
+
</label>
|
|
15
|
+
<InputText id="email" type="text" class="w-full"
|
|
16
|
+
v-model="data.email" :class="{ 'p-invalid': data.emailError }"
|
|
17
|
+
aria-describedby="email-help" />
|
|
18
|
+
<small id="email-help" class="p-error">{{ data.emailError }}</small>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<Button type="submit" label="Reset password" icon="pi pi-key" class="w-full"></Button>
|
|
22
|
+
|
|
23
|
+
</command-form>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
import InputText from "primevue/inputtext"
|
|
30
|
+
import Button from "primevue/button"
|
|
31
|
+
|
|
32
|
+
import { onMounted, ref } from "vue"
|
|
33
|
+
const isMounted = ref(false)
|
|
34
|
+
onMounted(() => isMounted.value = true)
|
|
35
|
+
|
|
36
|
+
import { useRouter } from 'vue-router'
|
|
37
|
+
const router = useRouter()
|
|
38
|
+
|
|
39
|
+
function handleDone({ parameters, result }) {
|
|
40
|
+
console.log("DONE RESULT", result)
|
|
41
|
+
const { authentication } = result
|
|
42
|
+
router.push({
|
|
43
|
+
name: 'user:sent',
|
|
44
|
+
params: {
|
|
45
|
+
authentication
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await new Promise(r=>setTimeout(r, 800))
|
|
51
|
+
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<style>
|
|
55
|
+
|
|
56
|
+
</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">Password changed</div>
|
|
5
|
+
<p class="mt-0 p-0 line-height-3">You have successfully changed your password.</p>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup>
|
|
11
|
+
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<style>
|
|
15
|
+
|
|
16
|
+
</style>
|