notificationStores.js raw

   1  import { writable, derived } from 'svelte/store';
   2  
   3  // ==================== Notification Categories ====================
   4  
   5  // Each category: { items: [], unreadCount: number, lastChecked: number }
   6  export const replyNotifications = writable({ items: [], unreadCount: 0, lastChecked: 0 });
   7  export const reactionNotifications = writable({ items: [], unreadCount: 0, lastChecked: 0 });
   8  export const zapNotifications = writable({ items: [], unreadCount: 0, lastChecked: 0 });
   9  
  10  // DM and channel unread counts are derived from chatStores (imported by components, not here)
  11  // These are placeholder counts updated by the notification system
  12  export const dmUnreadCount = writable(0);
  13  export const channelUnreadCount = writable(0);
  14  
  15  // ==================== Derived ====================
  16  
  17  // Total unread across all categories
  18  export const totalUnreadCount = derived(
  19      [replyNotifications, reactionNotifications, zapNotifications, dmUnreadCount, channelUnreadCount],
  20      ([$replies, $reactions, $zaps, $dms, $channels]) =>
  21          $replies.unreadCount + $reactions.unreadCount + $zaps.unreadCount + $dms + $channels
  22  );
  23  
  24  // ==================== Actions ====================
  25  
  26  /**
  27   * Add notification items to a category
  28   * @param {Function} store - The writable store to update
  29   * @param {Array} items - New notification items
  30   */
  31  function addNotifications(store, items) {
  32      store.update(cat => {
  33          const existingIds = new Set(cat.items.map(i => i.id));
  34          const newItems = items.filter(i => !existingIds.has(i.id));
  35          const newUnread = newItems.filter(i => i.created_at > cat.lastChecked).length;
  36          return {
  37              items: [...newItems, ...cat.items].sort((a, b) => b.created_at - a.created_at).slice(0, 100),
  38              unreadCount: cat.unreadCount + newUnread,
  39              lastChecked: cat.lastChecked
  40          };
  41      });
  42  }
  43  
  44  export function addReplyNotifications(items) { addNotifications(replyNotifications, items); }
  45  export function addReactionNotifications(items) { addNotifications(reactionNotifications, items); }
  46  export function addZapNotifications(items) { addNotifications(zapNotifications, items); }
  47  
  48  /**
  49   * Mark a category as read
  50   * @param {string} category - "replies" | "reactions" | "zaps"
  51   */
  52  export function markCategoryRead(category) {
  53      const stores = { replies: replyNotifications, reactions: reactionNotifications, zaps: zapNotifications };
  54      const store = stores[category];
  55      if (store) {
  56          store.update(cat => ({ ...cat, unreadCount: 0, lastChecked: Date.now() }));
  57      }
  58  }
  59  
  60  /**
  61   * Mark all notifications as read
  62   */
  63  export function markAllRead() {
  64      const now = Date.now();
  65      replyNotifications.update(c => ({ ...c, unreadCount: 0, lastChecked: now }));
  66      reactionNotifications.update(c => ({ ...c, unreadCount: 0, lastChecked: now }));
  67      zapNotifications.update(c => ({ ...c, unreadCount: 0, lastChecked: now }));
  68  }
  69  
  70  /**
  71   * Reset all notification state (on logout)
  72   */
  73  export function resetNotificationState() {
  74      replyNotifications.set({ items: [], unreadCount: 0, lastChecked: 0 });
  75      reactionNotifications.set({ items: [], unreadCount: 0, lastChecked: 0 });
  76      zapNotifications.set({ items: [], unreadCount: 0, lastChecked: 0 });
  77      dmUnreadCount.set(0);
  78      channelUnreadCount.set(0);
  79  }
  80