Files
webapp/app/composables/useNovu.ts
2026-01-07 09:10:35 +07:00

139 lines
3.4 KiB
TypeScript

import { Novu } from '@novu/js'
interface NovuNotification {
id: string
content: string
read: boolean
seen: boolean
createdAt: string
payload?: Record<string, unknown>
cta?: {
type: string
data: {
url?: string
}
}
}
let novuInstance: Novu | null = null
export const useNovu = () => {
const config = useRuntimeConfig()
const notifications = ref<NovuNotification[]>([])
const unreadCount = ref(0)
const isLoading = ref(false)
const isInitialized = ref(false)
const init = (subscriberId: string) => {
if (novuInstance || !subscriberId) return
const appId = config.public.novuAppId
const backendUrl = config.public.novuBackendUrl
const socketUrl = config.public.novuSocketUrl
if (!appId || !backendUrl) {
console.warn('[useNovu] Missing configuration: novuAppId or novuBackendUrl')
return
}
novuInstance = new Novu({
subscriberId,
applicationIdentifier: appId,
backendUrl,
socketUrl: socketUrl || undefined
})
isInitialized.value = true
// Subscribe to realtime updates
novuInstance.on('notifications.notification_received', () => {
loadNotifications()
})
// Load initial data
loadNotifications()
}
const loadNotifications = async () => {
if (!novuInstance) return
isLoading.value = true
try {
const result = await novuInstance.notifications.list({ limit: 20 })
const data = Array.isArray(result?.data)
? result.data
: (result?.data?.notifications || [])
notifications.value = data as NovuNotification[]
unreadCount.value = notifications.value.filter((n: NovuNotification) => !n.read).length
} catch (error) {
console.error('[useNovu] Failed to load notifications:', error)
} finally {
isLoading.value = false
}
}
const markAsRead = async (notificationId: string) => {
if (!novuInstance) return
try {
await novuInstance.notifications.read({ notificationId })
// Update locally
const notification = notifications.value.find((n: NovuNotification) => n.id === notificationId)
if (notification) {
notification.read = true
unreadCount.value = notifications.value.filter((n: NovuNotification) => !n.read).length
}
} catch (error) {
console.error('[useNovu] Failed to mark as read:', error)
}
}
const markAllAsRead = async () => {
if (!novuInstance) return
try {
await novuInstance.notifications.readAll()
notifications.value.forEach((n: NovuNotification) => { n.read = true })
unreadCount.value = 0
} catch (error) {
console.error('[useNovu] Failed to mark all as read:', error)
}
}
const markAsSeen = async (notificationId: string) => {
if (!novuInstance) return
try {
await novuInstance.notifications.archive({ notificationId })
const notification = notifications.value.find((n: NovuNotification) => n.id === notificationId)
if (notification) {
notification.seen = true
}
} catch (error) {
console.error('[useNovu] Failed to mark as seen:', error)
}
}
const destroy = () => {
if (novuInstance) {
novuInstance = null
isInitialized.value = false
notifications.value = []
unreadCount.value = 0
}
}
return {
init,
destroy,
notifications,
unreadCount,
isLoading,
isInitialized,
markAsRead,
markAllAsRead,
markAsSeen,
loadNotifications
}
}