139 lines
3.4 KiB
TypeScript
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
|
|
}
|
|
}
|