Skip to content

Notification Implementation Guide

Current State ✅

Your application already has a solid notification foundation:

  1. Firestore Storage: orgs/{orgId}/activityLogs collection
  2. PWA Support: Service worker handles push notifications
  3. Real-time Subscriptions: usePWANotifications hook listens to Firestore changes
  4. Notification Page: /org/{orgId}/hr/notifications displays activity logs
  5. Notification Types: Contracts, messages, applications, members

1. Firestore Structure

Organization-Level Notifications (Current - Keep This)

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

Perfect for organization-scoped activities:

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

User-Level Notifications (New - Add This)

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

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

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

2. Enhanced Notification Interface

typescript
// Add to src/types/organization.ts
export interface OrgActivityLog {
  id?: string;
  type:
    | 'org_created'
    | 'org_updated'
    | 'member_joined'
    | 'feature_released'
    | 'milestone'
    | 'member_removed'
    | 'contract_signed'
    | 'contract_member_signed'
    | 'contract_founder_signed'
    | 'message_sent'
    | 'application_stage_changed'
    | 'application_created'
    | 'application_approved'      // NEW
    | 'application_rejected'        // NEW
    | 'member_invited';
  userId?: string;
  targetUserId?: string;
  action: string;
  timestamp: string | Date;
  details?: string;
  metadata?: Record<string, any>;
  read?: boolean;                  // NEW: Read status
  readAt?: Date | Timestamp;       // NEW: When read
  actionUrl?: string;              // NEW: URL to navigate on click
}

// New type for user-level notifications
// Add to src/types/user.ts
export interface UserNotification {
  id?: string;
  type:
    | 'application_approved'      // From any org
    | 'application_rejected'       // From any org
    | 'message_received'          // Cross-org messages
    | 'system_update'             // Platform updates
    | 'reminder';                 // Personal reminders
  userId: string;                 // Target user
  orgId?: string;                 // Optional: org context
  title: string;
  body: string;
  read: boolean;
  readAt?: Date | Timestamp;
  createdAt: Date | Timestamp;
  actionUrl?: string;
  metadata?: Record<string, any>;
}

3. Implementation Steps

Step 1: Add Missing Notification Types

Update NotificationService.ts to add approval/rejection logging:

typescript
// Add to src/services/notifications/NotificationService.ts

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! 🎉`,
    actionUrl: `/org/${orgId}/applications/${applicationId}`,
    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.`,
    actionUrl: `/org/${orgId}/applications/${applicationId}`,
    metadata: {
      applicationId,
      candidateName,
      reason,
    },
  });
};

Step 2: Add Read/Unread Status

Update NotificationsPage.tsx to:

  • Show unread count badge
  • Mark notifications as read when viewed
  • Filter by read/unread status
typescript
// Add to NotificationsPage.tsx

const markAsRead = async (logId: string) => {
  if (!orgId) return;
  try {
    await updateDoc(doc(orgActivityLogsRef(orgId), logId), {
      read: true,
      readAt: serverTimestamp(),
    });
  } catch (error) {
    console.error('Error marking notification as read:', error);
  }
};

const unreadCount = logs.filter(log => !log.read).length;

Step 3: Update PWA Notification Hook

Enhance usePWANotifications.ts to handle new notification types:

typescript
// Update getNotificationTitle function
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
  }
}

Step 4: Add User-Level Notifications (Optional - Phase 2)

Create src/services/notifications/UserNotificationService.ts:

typescript
import { addDoc, collection, query, where, orderBy, getDocs, updateDoc, doc, serverTimestamp } from 'firebase/firestore';
import { db } from '../firebaseConfig';
import { UserNotification } from '../../types/user';

const userNotificationsRef = (userId: string) => 
  collection(db, 'users', userId, 'notifications');

export const createUserNotification = async (
  userId: string,
  notification: Omit<UserNotification, 'id' | 'createdAt' | 'read'>
): Promise<string> => {
  const docRef = await addDoc(userNotificationsRef(userId), {
    ...notification,
    read: false,
    createdAt: serverTimestamp(),
  });
  return docRef.id;
};

export const markUserNotificationAsRead = async (
  userId: string,
  notificationId: string
): Promise<void> => {
  await updateDoc(doc(userNotificationsRef(userId), notificationId), {
    read: true,
    readAt: serverTimestamp(),
  });
};

export const fetchUserNotifications = async (
  userId: string,
  limitCount: number = 50
): Promise<UserNotification[]> => {
  const q = query(
    userNotificationsRef(userId),
    orderBy('createdAt', 'desc'),
    limit(limitCount)
  );
  const snapshot = await getDocs(q);
  return snapshot.docs.map((doc) => ({
    id: doc.id,
    ...doc.data(),
  })) as UserNotification[];
};

4. Where to Use Notifications

Application Approval/Rejection

typescript
// In your application approval handler
import { logApplicationApproved, logApplicationRejected } from '../services/notifications/NotificationService';

// When approving
await logApplicationApproved(
  orgId,
  applicationId,
  candidateUserId,
  candidateName,
  currentUser.uid
);

// When rejecting
await logApplicationRejected(
  orgId,
  applicationId,
  candidateUserId,
  candidateName,
  currentUser.uid,
  rejectionReason
);

New Messages

typescript
// Already implemented in OrgPrivateMessagingService.ts
// Just ensure it's called when messages are sent
await logMessageSent(
  orgId,
  fromUserId,
  toUserId,
  messagePreview,
  fromUserName,
  toUserName
);

5. PWA Notification Delivery

Already Working! Your current setup automatically:

  • ✅ Receives Firestore changes via usePWANotifications hook
  • ✅ Shows browser notifications via service worker
  • ✅ Handles notification clicks (opens relevant page)

No changes needed - new notification types will automatically work!

6. Notification Page Enhancements

Add to NotificationsPage.tsx:

  • Unread count badge in header
  • "Mark all as read" button
  • Filter by read/unread
  • Click notification to navigate to action URL
  • Auto-mark as read when clicked

7. Firestore Security Rules

Add rules for user notifications:

javascript
// In firestore.rules
match /users/{userId}/notifications/{notificationId} {
  // Users can only read their own notifications
  allow read: if isAuthenticated() && request.auth.uid == userId;
  
  // Only system/cloud functions can create notifications
  allow create: if isAuthenticated() && isSuperAdmin();
  
  // Users can update their own notifications (mark as read)
  allow update: if isAuthenticated() && 
    request.auth.uid == userId &&
    request.resource.data.diff(resource.data).affectedKeys().hasOnly(['read', 'readAt']);
  
  // Only superadmins can delete
  allow delete: if isAuthenticated() && isSuperAdmin();
}

Benefits

  1. Scalable: Separate org and user notifications
  2. Flexible: Easy to add new notification types
  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

Next Steps

  1. ✅ Add application_approved and application_rejected types
  2. ✅ Add read/unread status to OrgActivityLog
  3. ✅ Update NotificationService with approval/rejection functions
  4. ✅ Update NotificationsPage with read/unread UI
  5. ✅ Update usePWANotifications hook for new types
  6. ⏳ (Optional) Add user-level notifications for Phase 2

Internal docs — access restricted via Cloudflare Zero Trust.