mirror of
https://github.com/VueFileManager/vuefilemanager.git
synced 2026-04-18 08:12:15 +00:00
backend notifications implementation
This commit is contained in:
@@ -1,69 +1,106 @@
|
||||
<template>
|
||||
<article class="z-20 relative flex items-start p-2.5 mb-1.5 space-x-4 rounded-xl dark:hover:bg-4x-dark-foreground hover:bg-light-background bg-opacity-80">
|
||||
<article
|
||||
class="delay-[3000ms] duration-700 transition-all relative z-20 mb-1.5 flex items-start space-x-4 rounded-xl p-2.5 dark:hover:bg-4x-dark-foreground"
|
||||
:class="{'bg-light-background/80': isUnread}"
|
||||
>
|
||||
<user-plus-icon
|
||||
v-if="notification.data.attributes.type === 'team-invitation'"
|
||||
size="20"
|
||||
class="vue-feather text-theme shrink-0"
|
||||
/>
|
||||
<upload-cloud-icon
|
||||
v-if="['file-request', 'remote-upload-done'].includes(notification.data.attributes.type)"
|
||||
size="20"
|
||||
class="vue-feather text-theme shrink-0"
|
||||
/>
|
||||
|
||||
<user-plus-icon v-if="notification.type === 'team-invitation'" size="20" class="vue-feather text-theme shrink-0" />
|
||||
<upload-cloud-icon v-if="['file-request', 'remote-upload-done'].includes(notification.type)" size="20" class="vue-feather text-theme shrink-0" />
|
||||
<div>
|
||||
<b class="mb-1.5 block text-sm font-extrabold">
|
||||
{{ notification.data.attributes.title }}
|
||||
</b>
|
||||
|
||||
<div>
|
||||
<b class="font-extrabold text-sm mb-1.5 block">
|
||||
{{ notification.title }}
|
||||
</b>
|
||||
<p class="mb-1.5 text-sm dark:text-gray-500">
|
||||
{{ notification.data.attributes.description }}
|
||||
</p>
|
||||
|
||||
<p class="text-sm mb-1.5 dark:text-gray-500">
|
||||
{{ notification.description }}
|
||||
</p>
|
||||
<div class="mb-4 flex items-center">
|
||||
<!--<MemberAvatar class="mr-2" :size="22" :is-border="false" :member="user" />-->
|
||||
<time class="block text-xs text-gray-400 dark:text-gray-400">
|
||||
{{ notification.data.attributes.created_at }}
|
||||
</time>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-4">
|
||||
<!--<MemberAvatar class="mr-2" :size="22" :is-border="false" :member="user" />-->
|
||||
<time class=" block text-xs dark:text-gray-400 text-gray-400">
|
||||
09. Mar. 2022, 08:27
|
||||
</time>
|
||||
</div>
|
||||
<!--Accept or decline team invitation-->
|
||||
<div v-if="notification.data.attributes.type === 'team-invitation'" class="flex items-center space-x-3">
|
||||
<div
|
||||
class="flex cursor-pointer items-center rounded-xl py-1.5 px-2 transition-colors hover:bg-green-100 dark:hover:bg-green-900"
|
||||
>
|
||||
<check-icon size="16" class="vue-feather mr-2 text-green-600 dark:text-green-600" />
|
||||
<span class="text-sm font-bold text-green-600 dark:text-green-600">
|
||||
{{ $t('Accept') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!--Accept or decline team invitation-->
|
||||
<div v-if="notification.type === 'team-invitation'" class="flex items-center space-x-3">
|
||||
<div class="flex items-center cursor-pointer py-1.5 px-2 rounded-xl transition-colors dark:hover:bg-green-900 hover:bg-green-100">
|
||||
<check-icon size="16" class="vue-feather dark:text-green-600 text-green-600 mr-2" />
|
||||
<span class="text-sm font-bold dark:text-green-600 text-green-600">
|
||||
{{ $t('Accept') }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex cursor-pointer items-center rounded-xl py-1.5 px-2 transition-colors hover:bg-rose-100 dark:hover:bg-rose-900"
|
||||
>
|
||||
<x-icon size="16" class="vue-feather mr-2 text-rose-600 dark:text-rose-600" />
|
||||
<span class="text-sm font-bold text-rose-600 dark:text-rose-600">
|
||||
{{ $t('Decline') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center cursor-pointer py-1.5 px-2 rounded-xl transition-colors dark:hover:bg-rose-900 hover:bg-rose-100">
|
||||
<x-icon size="16" class="vue-feather dark:text-rose-600 text-rose-600 mr-2" />
|
||||
<span class="text-sm font-bold dark:text-rose-600 text-rose-600">
|
||||
{{ $t('Decline') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Go to route-->
|
||||
<router-link v-if="['file-request', 'remote-upload-done'].includes(notification.type)" :to="{ name: 'Users' }" class="mt-4 flex items-center">
|
||||
<span class="mr-2 whitespace-nowrap text-xs font-bold">
|
||||
{{ $t('Show Files') }}
|
||||
</span>
|
||||
<chevron-right-icon size="16" class="text-theme vue-feather" />
|
||||
</router-link>
|
||||
</div>
|
||||
</article>
|
||||
<!--Go to route-->
|
||||
<router-link
|
||||
@click.native="closeCenter"
|
||||
v-if="action && action.type === 'route'"
|
||||
:to="{ name: action.params.route, params: { id: action.params.id } }"
|
||||
class="mt-4 flex items-center"
|
||||
>
|
||||
<span class="mr-2 whitespace-nowrap text-xs font-bold">
|
||||
{{ action.params.button }}
|
||||
</span>
|
||||
<chevron-right-icon size="16" class="text-theme vue-feather" />
|
||||
</router-link>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
<script>
|
||||
import {CheckIcon, XIcon, MailIcon, UserPlusIcon, UploadCloudIcon, ChevronRightIcon} from 'vue-feather-icons'
|
||||
import { CheckIcon, XIcon, MailIcon, UserPlusIcon, UploadCloudIcon, ChevronRightIcon } from 'vue-feather-icons'
|
||||
import MemberAvatar from '../FilesView/MemberAvatar'
|
||||
|
||||
export default {
|
||||
name: 'Notification',
|
||||
props: [
|
||||
'notification',
|
||||
],
|
||||
components: {
|
||||
MemberAvatar,
|
||||
ChevronRightIcon,
|
||||
UploadCloudIcon,
|
||||
UserPlusIcon,
|
||||
CheckIcon,
|
||||
MailIcon,
|
||||
XIcon,
|
||||
name: 'Notification',
|
||||
props: ['notification'],
|
||||
components: {
|
||||
MemberAvatar,
|
||||
ChevronRightIcon,
|
||||
UploadCloudIcon,
|
||||
UserPlusIcon,
|
||||
CheckIcon,
|
||||
MailIcon,
|
||||
XIcon,
|
||||
},
|
||||
computed: {
|
||||
action() {
|
||||
return this.notification.data.attributes.action
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isUnread: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeCenter() {
|
||||
this.$store.commit('TOGGLE_NOTIFICATION_CENTER')
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.isUnread = this.notification.data.attributes.read_at === null
|
||||
|
||||
setTimeout(() => this.isUnread = false, 1000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="fixed popup z-20 top-[27px] bottom-[27px] left-20 w-[360px]">
|
||||
|
||||
<!--Triangle-->
|
||||
<div class="z-20 absolute left-0 top-[102px] w-4 translate-x-[-15px] overflow-hidden inline-block">
|
||||
<div class="z-20 absolute left-0 top-[64px] w-4 translate-x-[-15px] overflow-hidden inline-block" :class="{'!top-[102px]': config.subscriptionType === 'metered'}">
|
||||
<div class="h-12 -rotate-45 transform origin-top-right dark:bg-2x-dark-foreground bg-white bg-opacity-80 backdrop-blur-2xl"></div>
|
||||
</div>
|
||||
|
||||
@@ -15,22 +15,26 @@
|
||||
</b>
|
||||
|
||||
<div class="px-2.5">
|
||||
<MobileActionButton icon="check-square" class="mb-2 dark:!bg-4x-dark-foreground">
|
||||
<MobileActionButton v-if="readNotifications.length || unreadNotifications.length" @click.native="deleteAllNotifications" icon="check-square" class="mb-2 dark:!bg-4x-dark-foreground">
|
||||
{{ $t('Clear all') }}
|
||||
</MobileActionButton>
|
||||
|
||||
<p v-if="!readNotifications.length && !unreadNotifications.length" class="text-sm mt-8">
|
||||
{{ $t("There aren't any notifications.") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<b class="dark-text-theme mt-1.5 block px-2.5 mb-2.5 text-xs text-gray-400">
|
||||
Today
|
||||
<b v-if="unreadNotifications.length" class="dark-text-theme mt-1.5 block px-2.5 mb-2.5 text-xs text-gray-400">
|
||||
{{ $t('Unread') }}
|
||||
</b>
|
||||
|
||||
<Notification :notification="notification" v-for="notification in todayNotifications" :key="notification.id" />
|
||||
<Notification :notification="notification" v-for="notification in unreadNotifications" :key="notification.id" />
|
||||
|
||||
<b class="dark-text-theme mt-2.5 block px-2.5 mb-2.5 text-xs text-gray-400">
|
||||
Later
|
||||
<b v-if="readNotifications.length" class="dark-text-theme mt-2.5 block px-2.5 mb-2.5 text-xs text-gray-400">
|
||||
{{ $t('Read') }}
|
||||
</b>
|
||||
|
||||
<Notification :notification="notification" v-for="notification in laterNotifications" :key="notification.id" />
|
||||
<Notification :notification="notification" v-for="notification in readNotifications" :key="notification.id" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
@@ -39,6 +43,7 @@
|
||||
<script>
|
||||
import Notification from "./Notification";
|
||||
import MobileActionButton from "../FilesView/MobileActionButton";
|
||||
import {mapGetters} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'NotificationCenter',
|
||||
@@ -46,6 +51,27 @@ export default {
|
||||
MobileActionButton,
|
||||
Notification
|
||||
},
|
||||
watch: {
|
||||
isVisibleNotificationCenter: function (visibility) {
|
||||
if (visibility) {
|
||||
axios.post('/api/user/notifications/read')
|
||||
.then(() => {
|
||||
this.$store.commit('UPDATE_NOTIFICATION_COUNT', 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'user', 'config', 'isVisibleNotificationCenter',
|
||||
]),
|
||||
readNotifications() {
|
||||
return this.user.data.relationships.readNotifications.data
|
||||
},
|
||||
unreadNotifications() {
|
||||
return this.user.data.relationships.unreadNotifications.data
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
laterNotifications: [
|
||||
@@ -86,6 +112,14 @@ export default {
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteAllNotifications() {
|
||||
axios.delete('/api/user/notifications')
|
||||
.then(() => {
|
||||
this.$store.commit('FLUSH_NOTIFICATIONS')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<!--Navigation-->
|
||||
<div class="mt-2 relative">
|
||||
<div @click="toggleNotificationCenter" class="relative button-icon inline-block cursor-pointer rounded-xl p-3 hover:bg-light-300 dark:hover:bg-4x-dark-foreground">
|
||||
<div @click="$store.commit('TOGGLE_NOTIFICATION_CENTER')" class="relative button-icon inline-block cursor-pointer rounded-xl p-3 hover:bg-light-300 dark:hover:bg-4x-dark-foreground">
|
||||
<bell-icon size="18" class="transition" :class="{'rotate-[30deg]': notificationCount}" />
|
||||
<span v-if="notificationCount" class="absolute z-[9] right-1.5 bottom-1.5 flex items-center justify-center w-4 h-4 bg-theme text-white rounded-full text-xs font-bold">
|
||||
{{ notificationCount }}
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NotificationCenter v-show="isNotificationCenter" />
|
||||
<NotificationCenter v-show="isVisibleNotificationCenter" />
|
||||
|
||||
<!--Navigation-->
|
||||
<div class="mt-6">
|
||||
@@ -97,7 +97,7 @@ export default {
|
||||
SunIcon,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isVisibleNavigationBars', 'isDarkMode', 'config', 'user']),
|
||||
...mapGetters(['isVisibleNavigationBars', 'isDarkMode', 'config', 'user', 'isVisibleNotificationCenter', 'notificationCount']),
|
||||
navigation() {
|
||||
if (this.user.data.attributes.role === 'admin') {
|
||||
return [
|
||||
@@ -140,15 +140,10 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
notificationCount: 2,
|
||||
isNotificationCenter: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleNotificationCenter() {
|
||||
this.notificationCount = 0
|
||||
this.isNotificationCenter = ! this.isNotificationCenter
|
||||
},
|
||||
isSection(section) {
|
||||
return this.$route.matched[0].name === section
|
||||
},
|
||||
|
||||
10
resources/js/store/modules/app.js
vendored
10
resources/js/store/modules/app.js
vendored
@@ -5,6 +5,8 @@ import router from '../../router'
|
||||
|
||||
const defaultState = {
|
||||
isVisibleNavigationBars: localStorage.getItem('is_navigation_bars') !== 'false',
|
||||
isVisibleNotificationCenter: false,
|
||||
notificationCount: 0,
|
||||
isDarkMode: false,
|
||||
isVisibleSidebar: localStorage.getItem('file_info_visibility') === 'true' || false,
|
||||
itemViewType: localStorage.getItem('preview_type') || 'list',
|
||||
@@ -167,10 +169,18 @@ const mutations = {
|
||||
UPDATE_DARK_MODE_STATUS(state, val) {
|
||||
state.isDarkMode = val
|
||||
},
|
||||
UPDATE_NOTIFICATION_COUNT(state, val) {
|
||||
state.notificationCount = val
|
||||
},
|
||||
TOGGLE_NOTIFICATION_CENTER(state) {
|
||||
state.isVisibleNotificationCenter = !state.isVisibleNotificationCenter
|
||||
},
|
||||
}
|
||||
|
||||
const getters = {
|
||||
isVisibleNotificationCenter: (state) => state.isVisibleNotificationCenter,
|
||||
isVisibleNavigationBars: (state) => state.isVisibleNavigationBars,
|
||||
notificationCount: (state) => state.notificationCount,
|
||||
isVisibleSidebar: (state) => state.isVisibleSidebar,
|
||||
itemViewType: (state) => state.itemViewType,
|
||||
requestedPlan: (state) => state.requestedPlan,
|
||||
|
||||
11
resources/js/store/modules/broadcasting.js
vendored
11
resources/js/store/modules/broadcasting.js
vendored
@@ -3,14 +3,15 @@ const defaultState = {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
runConnection: ({ commit, getters }) => {
|
||||
runConnection: ({ commit, getters, dispatch }) => {
|
||||
|
||||
commit('SET_RUNNING_COMMUNICATION')
|
||||
|
||||
Echo.private(`test.${getters.user.data.id}`)
|
||||
.listen('.Domain\\Notifications\\Events\\TestUpdate', (e) => {
|
||||
console.log(e);
|
||||
});
|
||||
Echo.private(`App.Users.Models.User.${getters.user.data.id}`)
|
||||
.notification(() => {
|
||||
// TODO: call sound
|
||||
dispatch('getAppData')
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
8
resources/js/store/modules/userAuth.js
vendored
8
resources/js/store/modules/userAuth.js
vendored
@@ -18,6 +18,7 @@ const actions = {
|
||||
resolve(response)
|
||||
|
||||
commit('RETRIEVE_USER', response.data)
|
||||
commit('UPDATE_NOTIFICATION_COUNT', response.data.data.relationships.unreadNotifications.data.length)
|
||||
|
||||
if (! getters.isRunningConnection) {
|
||||
dispatch('runConnection')
|
||||
@@ -172,6 +173,13 @@ const mutations = {
|
||||
}
|
||||
})
|
||||
},
|
||||
PUSH_NEW_NOTIFICATION(state, notification) {
|
||||
state.user.data.relationships.unreadNotifications.data.push(notification)
|
||||
},
|
||||
FLUSH_NOTIFICATIONS(state) {
|
||||
state.user.data.relationships.readNotifications.data = []
|
||||
state.user.data.relationships.unreadNotifications.data = []
|
||||
},
|
||||
}
|
||||
|
||||
const getters = {
|
||||
|
||||
Reference in New Issue
Block a user