Skip to content

Notification System Strategy

Current State

The application already has a notification infrastructure:

  • ✅ Firestore storage: orgs/{orgId}/activityLogs collection
  • ✅ PWA notification support via service worker
  • ✅ Real-time subscriptions via usePWANotifications hook
  • ✅ Notification page at /org/{orgId}/hr/notifications
  • ✅ Activity log types for contracts, messages, applications, members

1. Firestore Structure

Organization-Level Notifications (Current)

Path: orgs/{orgId}/activityLogs/{logId}

Keep this for organization-scoped activities:

  • Application approvals/rejections
  • Contract signings
  • Member invitations
  • Messages within organization
  • Application stage changes

User-Level Notifications (New)

Path: users/{userId}/notifications/{notificationId}

Add this for user-scoped notifications that may span multiple organizations:

  • Cross-org messages
  • Global system updates
  • Personal reminders
  • Application status across all orgs

2. Notification Types

Add Missing Types to OrgActivityLog:

typescript
type OrgActivityLogType =
  | 'application_approved'      // NEW: When candidate is approved
  | 'application_rejected'      // NEW: When candidate is rejected
  | 'application_stage_changed' // EXISTS
  | 'application_created'       // EXISTS
  | 'message_sent'              // EXISTS
  | 'contract_signed'           // EXISTS
  | 'member_invited'            // EXISTS
  | 'member_joined'             // EXISTS
  | 'member_removed'            // EXISTS
  // ... other existing types

User Notification Type:

typescript
type UserNotificationType =
  | 'application_approved'      // From any org
  | 'application_rejected'       // From any org
  | 'message_received'          // Cross-org messages
  | 'system_update'             // Platform updates
  | 'reminder'                  // Personal reminders

3. Notification Interface

typescript
interface Notification {
  id?: string;
  type: string;
  title: string;
  body: string;
  userId: string;              // Target user
  orgId?: string;              // Optional: org context
  read: boolean;                // NEW: Read status
  readAt?: Date | Timestamp;   // NEW: When read
  createdAt: Date | Timestamp;
  actionUrl?: string;           // URL to navigate on click
  metadata?: Record<string, any>;
}

4. Implementation Plan

Phase 1: Enhance Current System

  1. Add read and readAt fields to OrgActivityLog
  2. Add application_approved and application_rejected types
  3. Update NotificationService to log approvals/rejections
  4. Update NotificationsPage to show unread count and mark as read

Phase 2: Add User-Level Notifications

  1. Create users/{userId}/notifications collection
  2. Create UserNotificationService for user-scoped notifications
  3. Update usePWANotifications to listen to both org and user notifications
  4. Create global notifications page at /notifications

Phase 3: Enhanced Features

  1. Notification preferences (what to receive)
  2. Notification grouping (e.g., "3 new messages")
  3. Notification actions (e.g., "Approve" button in notification)
  4. Badge count in UI

Implementation Details

1. Update NotificationService

Add functions for application approvals/rejections:

typescript
export const logApplicationApproved = async (
  orgId: string,
  applicationId: string,
  candidateUserId: string,
  candidateName: string,
  approvedByUserId: string
): Promise<void> => {
  await createActivityLog(orgId, {
    type: 'application_approved',
    userId: approvedByUserId,
    targetUserId: candidateUserId,
    action: `Your application has been approved!`,
    metadata: {
      applicationId,
      candidateName,
    },
  });
};

export const logApplicationRejected = async (
  orgId: string,
  applicationId: string,
  candidateUserId: string,
  candidateName: string,
  rejectedByUserId: string,
  reason?: string
): Promise<void> => {
  await createActivityLog(orgId, {
    type: 'application_rejected',
    userId: rejectedByUserId,
    targetUserId: candidateUserId,
    action: `Your application was not selected at this time.`,
    metadata: {
      applicationId,
      candidateName,
      reason,
    },
  });
};

2. Update NotificationsPage

Add read/unread functionality:

typescript
// Mark notification as read
const markAsRead = async (logId: string) => {
  await updateDoc(orgActivityLogsRef(orgId).doc(logId), {
    read: true,
    readAt: serverTimestamp(),
  });
};

// Get unread count
const unreadCount = logs.filter(log => !log.read).length;

3. Update usePWANotifications Hook

Enhance to handle all notification types:

typescript
// Add notification titles for new types
function getNotificationTitle(type: OrgActivityLog['type']): string {
  switch (type) {
    case 'application_approved':
      return 'Application Approved! 🎉';
    case 'application_rejected':
      return 'Application Update';
    case 'message_sent':
      return 'New Message';
    // ... existing cases
  }
}

4. PWA Notification Delivery

The current PWA setup already handles notifications via:

  • Service worker (public/sw.js) - receives push events
  • usePWANotifications hook - subscribes to Firestore changes
  • showNotification utility - displays browser notifications

No changes needed - it will automatically work for new notification types!

Benefits of This Approach

  1. Scalable: Separate org and user notifications
  2. Flexible: Can add new notification types easily
  3. User-Friendly: Read/unread status, unread counts
  4. Real-Time: Firestore subscriptions provide instant updates
  5. PWA-Ready: Already integrated with service worker
  6. Backward Compatible: Enhances existing system without breaking changes

Next Steps

  1. ✅ Review and approve this strategy
  2. Implement Phase 1 (enhance current system)
  3. Test notification delivery via PWA
  4. Implement Phase 2 (user-level notifications)
  5. Add notification preferences UI

Internal docs — access restricted via Cloudflare Zero Trust.