Notification Implementation Guide
Current State ✅
Your application already has a solid notification foundation:
- Firestore Storage:
orgs/{orgId}/activityLogscollection - PWA Support: Service worker handles push notifications
- Real-time Subscriptions:
usePWANotificationshook listens to Firestore changes - Notification Page:
/org/{orgId}/hr/notificationsdisplays activity logs - Notification Types: Contracts, messages, applications, members
Recommended Architecture
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
usePWANotificationshook - ✅ 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
- Scalable: Separate org and user notifications
- Flexible: Easy to add new notification types
- User-Friendly: Read/unread status, unread counts
- Real-Time: Firestore subscriptions provide instant updates
- PWA-Ready: Already integrated with service worker
- Backward Compatible: Enhances existing system
Next Steps
- ✅ Add
application_approvedandapplication_rejectedtypes - ✅ Add read/unread status to
OrgActivityLog - ✅ Update
NotificationServicewith approval/rejection functions - ✅ Update
NotificationsPagewith read/unread UI - ✅ Update
usePWANotificationshook for new types - ⏳ (Optional) Add user-level notifications for Phase 2