develop-updateui #1
8
components.d.ts
vendored
8
components.d.ts
vendored
@@ -20,6 +20,7 @@ declare module 'vue' {
|
|||||||
CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||||
Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
||||||
DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||||
|
Drawer: typeof import('primevue/drawer')['default']
|
||||||
EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default']
|
EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default']
|
||||||
FloatLabel: typeof import('primevue/floatlabel')['default']
|
FloatLabel: typeof import('primevue/floatlabel')['default']
|
||||||
HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
||||||
@@ -28,7 +29,8 @@ declare module 'vue' {
|
|||||||
Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
||||||
LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
||||||
Message: typeof import('primevue/message')['default']
|
Message: typeof import('primevue/message')['default']
|
||||||
NotificationPopover: typeof import('./src/components/NotificationPopover.vue')['default']
|
NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
||||||
|
NotificationPopover: typeof import('./src/components/NotificationDrawer.vue')['default']
|
||||||
PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
||||||
Password: typeof import('primevue/password')['default']
|
Password: typeof import('primevue/password')['default']
|
||||||
RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
||||||
@@ -54,6 +56,7 @@ declare global {
|
|||||||
const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||||
const Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
const Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
||||||
const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||||
|
const Drawer: typeof import('primevue/drawer')['default']
|
||||||
const EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default']
|
const EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default']
|
||||||
const FloatLabel: typeof import('primevue/floatlabel')['default']
|
const FloatLabel: typeof import('primevue/floatlabel')['default']
|
||||||
const HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
const HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
||||||
@@ -62,7 +65,8 @@ declare global {
|
|||||||
const Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
const Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
||||||
const LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
const LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
||||||
const Message: typeof import('primevue/message')['default']
|
const Message: typeof import('primevue/message')['default']
|
||||||
const NotificationPopover: typeof import('./src/components/NotificationPopover.vue')['default']
|
const NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
||||||
|
const NotificationPopover: typeof import('./src/components/NotificationDrawer.vue')['default']
|
||||||
const PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
const PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
||||||
const Password: typeof import('primevue/password')['default']
|
const Password: typeof import('primevue/password')['default']
|
||||||
const RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
const RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Home from "@/components/icons/Home.vue";
|
|||||||
import Video from "@/components/icons/Video.vue";
|
import Video from "@/components/icons/Video.vue";
|
||||||
import Credit from "@/components/icons/Credit.vue";
|
import Credit from "@/components/icons/Credit.vue";
|
||||||
import Upload from "./icons/Upload.vue";
|
import Upload from "./icons/Upload.vue";
|
||||||
import NotificationPopover from "./NotificationPopover.vue";
|
import NotificationDrawer from "./NotificationDrawer.vue";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { createStaticVNode, ref } from "vue";
|
import { createStaticVNode, ref } from "vue";
|
||||||
|
|
||||||
@@ -25,7 +25,8 @@ const links = [
|
|||||||
{ href: "/profile", label: "Profile", icon: profileHoist, type: "a", className: 'w-12 h-12 rounded-2xl hover:bg-primary/15 flex' },
|
{ href: "/profile", label: "Profile", icon: profileHoist, type: "a", className: 'w-12 h-12 rounded-2xl hover:bg-primary/15 flex' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const notificationPopover = ref<InstanceType<typeof NotificationPopover>>();
|
const notificationPopover = ref<InstanceType<typeof NotificationDrawer>>();
|
||||||
|
const isNotificationOpen = ref(false);
|
||||||
|
|
||||||
const handleNotificationClick = (event: Event) => {
|
const handleNotificationClick = (event: Event) => {
|
||||||
notificationPopover.value?.toggle(event);
|
notificationPopover.value?.toggle(event);
|
||||||
@@ -37,12 +38,13 @@ const handleNotificationClick = (event: Event) => {
|
|||||||
<template v-for="i in links" :key="i.label">
|
<template v-for="i in links" :key="i.label">
|
||||||
<!-- Notification button with popover -->
|
<!-- Notification button with popover -->
|
||||||
<button
|
<button
|
||||||
|
name="notification"
|
||||||
v-if="i.type === 'notification'"
|
v-if="i.type === 'notification'"
|
||||||
@click="handleNotificationClick"
|
@click="handleNotificationClick"
|
||||||
v-tooltip="i.label"
|
v-tooltip="i.label"
|
||||||
:class="cn(i.className, 'relative')"
|
:class="cn(i.className, 'relative', isNotificationOpen && 'bg-primary/15')"
|
||||||
>
|
>
|
||||||
<component :is="i.icon" class="w-6 h-6" />
|
<component :is="i.icon" class="w-6 h-6" :filled="isNotificationOpen" />
|
||||||
<!-- Unread badge -->
|
<!-- Unread badge -->
|
||||||
<span class="absolute top-1 right-1 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-white"></span>
|
<span class="absolute top-1 right-1 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-white"></span>
|
||||||
</button>
|
</button>
|
||||||
@@ -57,9 +59,11 @@ const handleNotificationClick = (event: Event) => {
|
|||||||
<component :is="i.icon" class="w-6 h-6" :filled="$route.path === i.href" />
|
<component :is="i.icon" class="w-6 h-6" :filled="$route.path === i.href" />
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<NotificationDrawer ref="notificationPopover" @change="(val) => isNotificationOpen = val" />
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<NotificationPopover ref="notificationPopover" />
|
|
||||||
|
|
||||||
<main class="flex flex-1 overflow-hidden md:ps-18">
|
<main class="flex flex-1 overflow-hidden md:ps-18">
|
||||||
<div class="flex-1 overflow-auto p-4 bg-white rounded-lg md:(mr-2 mb-2) min-h-[calc(100vh-8rem)]">
|
<div class="flex-1 overflow-auto p-4 bg-white rounded-lg md:(mr-2 mb-2) min-h-[calc(100vh-8rem)]">
|
||||||
|
|||||||
207
src/components/NotificationDrawer.vue
Normal file
207
src/components/NotificationDrawer.vue
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import NotificationItem from '@/routes/notification/components/NotificationItem.vue';
|
||||||
|
import { onClickOutside } from '@vueuse/core';
|
||||||
|
|
||||||
|
// Emit event when visibility changes
|
||||||
|
const emit = defineEmits(['change']);
|
||||||
|
|
||||||
|
type NotificationType = 'info' | 'success' | 'warning' | 'error' | 'video' | 'payment' | 'system';
|
||||||
|
|
||||||
|
interface Notification {
|
||||||
|
id: string;
|
||||||
|
type: NotificationType;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
time: string;
|
||||||
|
read: boolean;
|
||||||
|
actionUrl?: string;
|
||||||
|
actionLabel?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const drawerRef = ref(null);
|
||||||
|
|
||||||
|
// Mock notifications data
|
||||||
|
const notifications = ref<Notification[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'video',
|
||||||
|
title: 'Video processing complete',
|
||||||
|
message: 'Your video "Summer Vacation 2024" has been successfully processed.',
|
||||||
|
time: '2 min ago',
|
||||||
|
read: false,
|
||||||
|
actionUrl: '/video',
|
||||||
|
actionLabel: 'View'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'payment',
|
||||||
|
title: 'Payment successful',
|
||||||
|
message: 'Your subscription to Pro Plan has been renewed successfully.',
|
||||||
|
time: '1 hour ago',
|
||||||
|
read: false,
|
||||||
|
actionUrl: '/payments-and-plans',
|
||||||
|
actionLabel: 'Receipt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: 'warning',
|
||||||
|
title: 'Storage almost full',
|
||||||
|
message: 'You have used 85% of your storage quota.',
|
||||||
|
time: '3 hours ago',
|
||||||
|
read: false,
|
||||||
|
actionUrl: '/payments-and-plans',
|
||||||
|
actionLabel: 'Upgrade'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
type: 'success',
|
||||||
|
title: 'Upload successful',
|
||||||
|
message: 'Your video "Product Demo v2" has been uploaded.',
|
||||||
|
time: '1 day ago',
|
||||||
|
read: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const unreadCount = computed(() => notifications.value.filter(n => !n.read).length);
|
||||||
|
|
||||||
|
const toggle = (event?: Event) => {
|
||||||
|
console.log(event);
|
||||||
|
// Prevent event propagation to avoid immediate closure by onClickOutside
|
||||||
|
if (event) {
|
||||||
|
// We don't stop propagation here to let other listeners work,
|
||||||
|
// but we might need to ignore the trigger element in onClickOutside
|
||||||
|
// However, since the trigger is outside this component, simple toggle logic works
|
||||||
|
// if we use a small delay or ignore ref.
|
||||||
|
// Best approach: "toggle" usually comes from a button click.
|
||||||
|
}
|
||||||
|
visible.value = !visible.value;
|
||||||
|
console.log(visible.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle click outside
|
||||||
|
onClickOutside(drawerRef, (event) => {
|
||||||
|
// We can just set visible to false.
|
||||||
|
// Note: If the toggle button is clicked, it might toggle it back on immediately
|
||||||
|
// if the click event propagates.
|
||||||
|
// The user calls `toggle` from the parent's button click handler.
|
||||||
|
// If that button is outside `drawerRef` (which it is), this will fire.
|
||||||
|
// To avoid conflict, we usually check if the target is the trigger.
|
||||||
|
// But we don't have access to the trigger ref here.
|
||||||
|
// A common workaround is to use `ignore` option if we had the ref,
|
||||||
|
// or relying on the fact that if this fires, it sets specific state to false.
|
||||||
|
// If the button click then fires `toggle`, it might set it true again.
|
||||||
|
// Optimization: check if visible is true before closing.
|
||||||
|
if (visible.value) {
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
ignore: ['.press-animated', '[name="notification"]'] // Assuming the trigger button has this class or we can suggest adding a class to the trigger
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleMarkRead = (id: string) => {
|
||||||
|
const notification = notifications.value.find(n => n.id === id);
|
||||||
|
if (notification) notification.read = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (id: string) => {
|
||||||
|
notifications.value = notifications.value.filter(n => n.id !== id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMarkAllRead = () => {
|
||||||
|
notifications.value.forEach(n => n.read = true);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(visible, (val) => {
|
||||||
|
emit('change', val);
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({ toggle });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<Transition
|
||||||
|
enter-active-class="transition-all duration-300 ease-out"
|
||||||
|
enter-from-class="opacity-0 -translate-x-4"
|
||||||
|
enter-to-class="opacity-100 translate-x-0"
|
||||||
|
leave-active-class="transition-all duration-200 ease-in"
|
||||||
|
leave-from-class="opacity-100 translate-x-0"
|
||||||
|
leave-to-class="opacity-0 -translate-x-4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="visible"
|
||||||
|
ref="drawerRef"
|
||||||
|
class="fixed top-0 left-[80px] bottom-0 w-[380px] bg-white rounded-2xl border border-gray-300 p-3 z-50 flex flex-col shadow-lg my-3"
|
||||||
|
>
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex items-center justify-between p-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<h3 class="font-semibold text-gray-900">Notifications</h3>
|
||||||
|
<span
|
||||||
|
v-if="unreadCount > 0"
|
||||||
|
class="px-2 py-0.5 text-xs font-medium bg-primary text-white rounded-full"
|
||||||
|
>
|
||||||
|
{{ unreadCount }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="unreadCount > 0"
|
||||||
|
@click="handleMarkAllRead"
|
||||||
|
class="text-sm text-primary hover:underline font-medium"
|
||||||
|
>
|
||||||
|
Mark all read
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notification List -->
|
||||||
|
<div class="flex flex-col flex-1 overflow-y-auto gap-2">
|
||||||
|
<template v-if="notifications.length > 0">
|
||||||
|
<div
|
||||||
|
v-for="notification in notifications"
|
||||||
|
:key="notification.id"
|
||||||
|
class="border-b border-gray-50 last:border-0"
|
||||||
|
>
|
||||||
|
<NotificationItem
|
||||||
|
:notification="notification"
|
||||||
|
@mark-read="handleMarkRead"
|
||||||
|
@delete="handleDelete"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Empty state -->
|
||||||
|
<div v-else class="py-12 text-center">
|
||||||
|
<span class="i-lucide-bell-off w-12 h-12 text-gray-300 mx-auto block mb-3"></span>
|
||||||
|
<p class="text-gray-500 text-sm">No notifications</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div v-if="notifications.length > 0" class="p-3 border-t border-gray-100 bg-gray-50/50">
|
||||||
|
<router-link
|
||||||
|
to="/notification"
|
||||||
|
class="block w-full text-center text-sm text-primary font-medium hover:underline"
|
||||||
|
@click="visible = false"
|
||||||
|
>
|
||||||
|
View all notifications
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
.notification-popover {
|
||||||
|
border-radius: 16px !important;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12) !important;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.08) !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-popover .p-popover-content {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import Popover from 'primevue/popover';
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import NotificationItem from '@/routes/notification/components/NotificationItem.vue';
|
|
||||||
|
|
||||||
type NotificationType = 'info' | 'success' | 'warning' | 'error' | 'video' | 'payment' | 'system';
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
id: string;
|
|
||||||
type: NotificationType;
|
|
||||||
title: string;
|
|
||||||
message: string;
|
|
||||||
time: string;
|
|
||||||
read: boolean;
|
|
||||||
actionUrl?: string;
|
|
||||||
actionLabel?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const popover = ref<InstanceType<typeof Popover>>();
|
|
||||||
|
|
||||||
// Mock notifications data
|
|
||||||
const notifications = ref<Notification[]>([
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'video',
|
|
||||||
title: 'Video processing complete',
|
|
||||||
message: 'Your video "Summer Vacation 2024" has been successfully processed.',
|
|
||||||
time: '2 min ago',
|
|
||||||
read: false,
|
|
||||||
actionUrl: '/video',
|
|
||||||
actionLabel: 'View'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
type: 'payment',
|
|
||||||
title: 'Payment successful',
|
|
||||||
message: 'Your subscription to Pro Plan has been renewed successfully.',
|
|
||||||
time: '1 hour ago',
|
|
||||||
read: false,
|
|
||||||
actionUrl: '/payments-and-plans',
|
|
||||||
actionLabel: 'Receipt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
type: 'warning',
|
|
||||||
title: 'Storage almost full',
|
|
||||||
message: 'You have used 85% of your storage quota.',
|
|
||||||
time: '3 hours ago',
|
|
||||||
read: false,
|
|
||||||
actionUrl: '/payments-and-plans',
|
|
||||||
actionLabel: 'Upgrade'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
type: 'success',
|
|
||||||
title: 'Upload successful',
|
|
||||||
message: 'Your video "Product Demo v2" has been uploaded.',
|
|
||||||
time: '1 day ago',
|
|
||||||
read: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const unreadCount = computed(() => notifications.value.filter(n => !n.read).length);
|
|
||||||
|
|
||||||
const toggle = (event: Event) => {
|
|
||||||
popover.value?.toggle(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMarkRead = (id: string) => {
|
|
||||||
const notification = notifications.value.find(n => n.id === id);
|
|
||||||
if (notification) notification.read = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
|
||||||
notifications.value = notifications.value.filter(n => n.id !== id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMarkAllRead = () => {
|
|
||||||
notifications.value.forEach(n => n.read = true);
|
|
||||||
};
|
|
||||||
|
|
||||||
defineExpose({ toggle });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Popover ref="popover" appendTo="body" :pt="{
|
|
||||||
root: { class: 'notification-popover' },
|
|
||||||
content: { class: 'p-0' }
|
|
||||||
}">
|
|
||||||
<div class="w-[380px] max-h-[480px] flex flex-col">
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="flex items-center justify-between p-4 border-b border-gray-100">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<h3 class="font-semibold text-gray-900">Notifications</h3>
|
|
||||||
<span
|
|
||||||
v-if="unreadCount > 0"
|
|
||||||
class="px-2 py-0.5 text-xs font-medium bg-primary text-white rounded-full"
|
|
||||||
>
|
|
||||||
{{ unreadCount }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
v-if="unreadCount > 0"
|
|
||||||
@click="handleMarkAllRead"
|
|
||||||
class="text-sm text-primary hover:underline font-medium"
|
|
||||||
>
|
|
||||||
Mark all read
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Notification List -->
|
|
||||||
<div class="flex-1 overflow-y-auto">
|
|
||||||
<template v-if="notifications.length > 0">
|
|
||||||
<div
|
|
||||||
v-for="notification in notifications"
|
|
||||||
:key="notification.id"
|
|
||||||
class="border-b border-gray-50 last:border-0"
|
|
||||||
>
|
|
||||||
<NotificationItem
|
|
||||||
:notification="notification"
|
|
||||||
@mark-read="handleMarkRead"
|
|
||||||
@delete="handleDelete"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Empty state -->
|
|
||||||
<div v-else class="py-12 text-center">
|
|
||||||
<span class="i-lucide-bell-off w-12 h-12 text-gray-300 mx-auto block mb-3"></span>
|
|
||||||
<p class="text-gray-500 text-sm">No notifications</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<div v-if="notifications.length > 0" class="p-3 border-t border-gray-100 bg-gray-50/50">
|
|
||||||
<router-link
|
|
||||||
to="/notification"
|
|
||||||
class="block w-full text-center text-sm text-primary font-medium hover:underline"
|
|
||||||
@click="popover?.hide()"
|
|
||||||
>
|
|
||||||
View all notifications
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.notification-popover {
|
|
||||||
border-radius: 16px !important;
|
|
||||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12) !important;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.08) !important;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-popover .p-popover-content {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -115,7 +115,7 @@ const handleClearAll = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="notification-page">
|
<div>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Notifications"
|
title="Notifications"
|
||||||
description="Stay updated with your latest activities and alerts."
|
description="Stay updated with your latest activities and alerts."
|
||||||
@@ -124,7 +124,7 @@ const handleClearAll = () => {
|
|||||||
{ label: 'Notifications' }
|
{ label: 'Notifications' }
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
|
<div class="w-full max-w-4xl mx-auto mt-6">
|
||||||
<div class="notification-container bg-white rounded-2xl border border-gray-200 p-6 shadow-sm">
|
<div class="notification-container bg-white rounded-2xl border border-gray-200 p-6 shadow-sm">
|
||||||
<NotificationActions
|
<NotificationActions
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -148,10 +148,5 @@ const handleClearAll = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
|
</template>
|
||||||
<style scoped>
|
|
||||||
.notification-page {
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user