- Added real-time event emissions for guest creation, update, and deletion across various API routes. - Enhanced guest request submission to include real-time notifications. - Introduced real-time updates for table creation to keep the admin dashboard synchronized. - Improved error handling for real-time event emissions to ensure reliability during guest and table operations.
15 KiB
Real-Time Data Synchronization - Complete Implementation Guide
📋 Executive Summary
Status: Critical fixes completed. Remaining tasks documented for phased implementation.
Key Achievements:
- ✅ RSVP → Master Guest List sync fixed
- ✅ Seating Chart real-time updates added
- ✅ Reports Summary real-time updates added
- ✅ Guest update/delete events implemented
- ✅ Polling intervals standardized
Remaining Work:
- ⚠️ Table management routes need POST/PUT/DELETE handlers
- ⚠️ Some components still use hardcoded polling intervals
- ⚠️ Guest requests need real-time events
🔍 Current Architecture
Real-Time Infrastructure
- Technology: Server-Sent Events (SSE) via
/api/realtime - Transport: EventSource API (browser-native, Next.js compatible)
- Backend: Redis Pub/Sub for multi-instance support
- Fallback: In-memory broadcast if Redis unavailable
- Auto-reconnect: Built into EventSource
Event Flow
API Route (data change)
↓
RealtimeManager.sendEvent()
↓
POST /api/realtime
↓
Redis Pub/Sub (if available)
↓
SSE Clients (/api/realtime GET)
↓
Client Components (subscribe)
↓
UI Updates
✅ Phase 1: Critical Fixes (COMPLETED)
1.1 RSVP Status Sync
Problem: RSVP confirmations didn't update Master Guest List
Solution:
- Added
rsvpStatusfield updates in RSVP routes - Added
refreshGuestList()call inonRSVPUpdatehandler - Added real-time events with
rsvpStatusin payload
Files Modified:
src/app/api/rsvp/submit/route.tssrc/app/api/rsvp/update/route.tssrc/app/api/admin/rsvp/on-behalf/route.tssrc/components/features/admin/admin-dashboard.tsx
1.2 Seating Chart Real-Time
Problem: Seating assignments required manual refresh
Solution:
- Added subscriptions to
seating,guest, andtableevents - Auto-refresh on table assignment changes
Files Modified:
src/components/features/admin/seating-chart-enhanced.tsx
1.3 Reports Summary Real-Time
Problem: Reports showed stale data
Solution:
- Added subscriptions to
rsvp,guest,seating, andtableevents - Auto-refresh when any relevant data changes
Files Modified:
src/components/features/admin/reports-exports.tsx
1.4 Guest Events
Problem: Guest updates/deletions didn't emit events
Solution:
- Added
guest.create,guest.update,guest.deleteevents - Added bulk operation events
Files Modified:
src/app/api/admin/guests/route.ts(POST, PUT, DELETE)src/app/api/admin/guests/bulk-update/route.tssrc/app/api/admin/guests/bulk-delete/route.ts
⚠️ Phase 2: Standardization (TODO)
2.1 Polling Intervals
Created: src/lib/polling-intervals.ts
Files to Update:
-
src/components/features/dashboard/guest-directory.tsx// Change from: setInterval(loadWithCleanup, 10000) // To: setInterval(loadWithCleanup, POLLING_INTERVALS.CRITICAL) -
src/components/features/admin/guest-requests.tsx// Change from: setInterval(fetchWithCleanup, 30000) // To: setInterval(fetchWithCleanup, POLLING_INTERVALS.CRITICAL) // Also add real-time subscription -
src/components/features/admin/activity-feed.tsx// Add real-time + use POLLING_INTERVALS.BACKGROUND
2.2 Add Real-Time to Guest Requests
File: src/components/features/admin/guest-requests.tsx
Implementation:
useEffect(() => {
const { realtimeManager } = require('@/lib/realtime');
realtimeManager.connect();
const unsubscribe = realtimeManager.subscribe('guest-request', (event) => {
if (event.action === 'create' || event.action === 'update') {
fetchWithCleanup();
}
});
return () => unsubscribe();
}, []);
Also need: Add events to src/app/api/admin/guest-requests/route.ts (if POST/PUT handlers exist)
⚠️ Phase 3: Missing API Routes (TODO)
3.1 Table Management Routes
Issue: Component calls POST/PATCH/DELETE on /api/admin/tables, but routes don't exist
Files to Create/Update:
src/app/api/admin/tables/route.ts- Add POST handlersrc/app/api/admin/tables/[id]/route.ts- Add PATCH and DELETE handlers
Implementation Example:
// src/app/api/admin/tables/route.ts
// POST - Create table
export async function POST(request: NextRequest) {
try {
const session = await verifyAdminSession(request);
if (!session || !session.authenticated) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json();
const { name, capacity } = body;
const newTable = await prisma.table.create({
data: { name, capacity: capacity || 8 },
});
// Emit real-time event
try {
const { RealtimeManager } = await import('@/lib/realtime');
await RealtimeManager.sendEvent({
type: 'table',
action: 'create',
data: {
tableId: newTable.id,
name: newTable.name,
capacity: newTable.capacity,
},
timestamp: new Date().toISOString(),
});
} catch (error) {
console.error('Failed to emit table create event:', error);
}
return NextResponse.json({ success: true, table: newTable }, { status: 201 });
} catch (error) {
return NextResponse.json({ error: "Failed to create table" }, { status: 500 });
}
}
// src/app/api/admin/tables/[id]/route.ts
// PATCH - Update table
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await verifyAdminSession(request);
if (!session || !session.authenticated) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id } = await params;
const body = await request.json();
const { name, capacity } = body;
const updatedTable = await prisma.table.update({
where: { id },
data: { name, capacity },
});
// Emit real-time event
try {
const { RealtimeManager } = await import('@/lib/realtime');
await RealtimeManager.sendEvent({
type: 'table',
action: 'update',
data: {
tableId: updatedTable.id,
name: updatedTable.name,
capacity: updatedTable.capacity,
},
timestamp: new Date().toISOString(),
});
} catch (error) {
console.error('Failed to emit table update event:', error);
}
return NextResponse.json({ success: true, table: updatedTable });
} catch (error) {
return NextResponse.json({ error: "Failed to update table" }, { status: 500 });
}
}
// DELETE - Delete table
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await verifyAdminSession(request);
if (!session || !session.authenticated) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id } = await params;
const deletedTable = await prisma.table.delete({
where: { id },
});
// Emit real-time event
try {
const { RealtimeManager } = await import('@/lib/realtime');
await RealtimeManager.sendEvent({
type: 'table',
action: 'delete',
data: {
tableId: deletedTable.id,
name: deletedTable.name,
},
timestamp: new Date().toISOString(),
});
} catch (error) {
console.error('Failed to emit table delete event:', error);
}
return NextResponse.json({ success: true, message: "Table deleted" });
} catch (error) {
return NextResponse.json({ error: "Failed to delete table" }, { status: 500 });
}
}
📊 Component Real-Time Status
Admin Dashboard Components
| Component | Real-Time | Polling | Status |
|---|---|---|---|
| Master Guest List | ✅ RSVP, Guest | ❌ | ✅ FIXED |
| RSVP Section | ✅ RSVP | ❌ | ✅ Working |
| Stats | ✅ RSVP | 60s | ✅ Working |
| Seating Chart | ✅ Seating, Guest, Table | ❌ | ✅ FIXED |
| Reports Summary | ✅ RSVP, Guest, Seating, Table | ❌ | ✅ FIXED |
| Music Requests | ✅ Music | ❌ | ✅ Working |
| Gallery | ✅ Gallery | ❌ | ✅ Working |
| Guestbook | ✅ Guestbook | ❌ | ✅ Working |
| Tributes | ✅ Tribute | ❌ | ✅ Working |
| Guest Requests | ❌ | 30s | ⚠️ TODO |
| Activity Feed | ❌ | 30s | ⚠️ TODO |
| Who's Who | ✅ RSVP | 30s | ⚠️ Could remove polling |
Guest Dashboard Components
| Component | Real-Time | Polling | Status |
|---|---|---|---|
| Guest Profile | ✅ RSVP, Seating | 30s | ✅ Working |
| Table Reveal | ✅ Seating | ❌ | ✅ Working |
| Music | ✅ Music | 30s | ✅ Working |
| Gallery | ❌ | ❌ | ⚠️ Should add real-time |
| Guestbook | ✅ Guestbook | 30s | ✅ Working |
| Guest Directory | ✅ RSVP, Guest | 10s | ⚠️ Should use 30s |
| Who's Who | ✅ RSVP | ❌ | ✅ Working |
🎯 Implementation Priority
✅ COMPLETED (Phase 1)
- ✅ RSVP → Master Guest List sync
- ✅ Seating Chart real-time
- ✅ Reports Summary real-time
- ✅ Guest update/delete events
- ✅ Polling intervals constants
🔄 IN PROGRESS (Phase 2)
- ⚠️ Update polling intervals to use constants
- ⚠️ Add real-time to Guest Requests
- ⚠️ Add real-time to Activity Feed
📋 PLANNED (Phase 3)
- ⚠️ Add table management routes (POST/PATCH/DELETE)
- ⚠️ Add table events
- ⚠️ Add guest-request events
🚀 FUTURE (Phase 4)
- ⚠️ Create unified
useRealtimeSynchook - ⚠️ Refactor components to use unified hook
- ⚠️ Add optimistic updates
- ⚠️ Add connection status indicators
📝 Code Examples
Example 1: Adding Real-Time to a Component
// Before
useEffect(() => {
fetchData();
const interval = setInterval(fetchData, 30000);
return () => clearInterval(interval);
}, []);
// After
import { POLLING_INTERVALS } from '@/lib/polling-intervals';
useEffect(() => {
fetchData();
// Real-time subscription
const { realtimeManager } = require('@/lib/realtime');
realtimeManager.connect();
const unsubscribe = realtimeManager.subscribe('your-event-type', (event) => {
if (event.action === 'create' || event.action === 'update') {
fetchData();
}
});
// Polling fallback
const interval = setInterval(fetchData, POLLING_INTERVALS.CRITICAL);
return () => {
unsubscribe();
clearInterval(interval);
};
}, []);
Example 2: Emitting Events from API Routes
// After data modification
try {
const { RealtimeManager } = await import('@/lib/realtime');
await RealtimeManager.sendEvent({
type: 'your-event-type',
action: 'create' | 'update' | 'delete',
data: {
// Include relevant data for clients
id: updatedItem.id,
// ... other fields
},
timestamp: new Date().toISOString(),
});
} catch (error) {
// Don't fail the API call if real-time fails
console.error('Failed to emit real-time event:', error);
}
Example 3: Debounced Real-Time Updates
useEffect(() => {
const { realtimeManager } = require('@/lib/realtime');
realtimeManager.connect();
let debounceTimeout: NodeJS.Timeout | null = null;
const unsubscribe = realtimeManager.subscribe('event-type', (event) => {
// Clear existing timeout
if (debounceTimeout) clearTimeout(debounceTimeout);
// Debounce rapid updates
debounceTimeout = setTimeout(() => {
fetchData();
}, 1000); // 1 second debounce
});
return () => {
if (debounceTimeout) clearTimeout(debounceTimeout);
unsubscribe();
};
}, []);
🧪 Testing Guide
Manual Testing
-
RSVP Sync Test:
- Open admin dashboard in two browser windows
- In window 1: Go to RSVP section
- In window 2: Go to Master Guest List
- Have a guest confirm RSVP
- Verify: Master Guest List updates within 1-2 seconds
-
Seating Chart Test:
- Open seating chart
- Assign guest to table (in another window or via API)
- Verify: Seating chart updates automatically
-
Reports Test:
- Open Reports & Exports
- Have guest confirm/decline RSVP
- Verify: Headcount numbers update automatically
Automated Testing
// Example test for real-time sync
describe('RSVP → Master Guest List Sync', () => {
it('should update guest list when RSVP is confirmed', async () => {
// 1. Mock real-time event
// 2. Trigger RSVP confirmation
// 3. Verify guest list refresh was called
// 4. Verify guest status updated
});
});
🔧 Troubleshooting
Issue: Real-Time Not Working
Check:
- SSE connection established? (Check Network tab for
/api/realtime) - Redis available? (Check server logs)
- Events being emitted? (Check API route logs)
- Components subscribed? (Check component useEffect)
Issue: Too Many Refreshes
Solution: Increase debounce time or add more specific event filtering
Issue: Stale Data
Solution:
- Check cache invalidation tags
- Verify real-time events include all necessary data
- Ensure polling fallback is working
📈 Performance Considerations
Current Optimizations
- ✅ Debouncing (500ms-2000ms)
- ✅ Event filtering (only subscribe to needed events)
- ✅ Parallel event emission (bulk operations)
- ✅ Non-blocking (events don't fail API calls)
Recommendations
- Monitor SSE connection count
- Track event emission rate
- Consider batching events for bulk operations
- Add connection pooling if needed
🎓 Best Practices
- Always use debouncing for rapid updates
- Keep polling as fallback - real-time can fail
- Don't block API responses - emit events asynchronously
- Include relevant data in events - avoid unnecessary refetches
- Clean up subscriptions - prevent memory leaks
- Handle errors gracefully - real-time failures shouldn't break features
📚 Reference
- Real-Time Manager:
src/lib/realtime.ts - SSE Endpoint:
src/app/api/realtime/route.ts - Admin Realtime Hook:
src/hooks/use-admin-realtime.ts - Polling Constants:
src/lib/polling-intervals.ts - Full Audit:
REALTIME_AUDIT.md
✅ Completion Checklist
Critical Fixes
- RSVP → Master Guest List sync
- Seating Chart real-time
- Reports Summary real-time
- Guest update/delete events
- Polling intervals constants
Standardization
- Update all polling intervals
- Add real-time to Guest Requests
- Add real-time to Activity Feed
Missing Routes
- Add table POST/PATCH/DELETE routes
- Add table events
- Add guest-request events
Enhancement
- Create unified hook
- Refactor components
- Add connection status UI
- Performance testing
Last Updated: Based on comprehensive audit and critical fixes implementation.