- Build complete Next.js CRM for commercial real estate - Add authentication with JWT sessions and role-based access - Add GoHighLevel API integration for contacts, conversations, opportunities - Add AI-powered Control Center with tool calling - Add Setup page with onboarding checklist (/setup) - Add sidebar navigation with Setup menu item - Fix type errors in onboarding API, GHL services, and control center tools - Add Prisma schema with SQLite for local development - Add UI components with clay morphism design system Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
82 lines
2.3 KiB
TypeScript
82 lines
2.3 KiB
TypeScript
'use client';
|
|
|
|
import { createContext, useContext, useEffect, useState } from 'react';
|
|
import { RealtimeEvent, RealtimeMessage, REALTIME_EVENTS } from '@/lib/realtime/events';
|
|
|
|
interface RealtimeContextValue {
|
|
isConnected: boolean;
|
|
subscribe: <T = any>(event: RealtimeEvent, handler: (data: T) => void) => () => void;
|
|
lastEvent: RealtimeMessage | null;
|
|
}
|
|
|
|
const RealtimeContext = createContext<RealtimeContextValue | null>(null);
|
|
|
|
export function RealtimeProvider({ children }: { children: React.ReactNode }) {
|
|
const [isConnected, setIsConnected] = useState(false);
|
|
const [lastEvent, setLastEvent] = useState<RealtimeMessage | null>(null);
|
|
const [handlers, setHandlers] = useState<Map<RealtimeEvent, Set<(data: any) => void>>>(new Map());
|
|
|
|
useEffect(() => {
|
|
const eventSource = new EventSource('/api/v1/realtime/events');
|
|
|
|
eventSource.onopen = () => {
|
|
setIsConnected(true);
|
|
};
|
|
|
|
eventSource.onmessage = (event) => {
|
|
try {
|
|
const message = JSON.parse(event.data);
|
|
if (message.type === 'connected') return;
|
|
|
|
setLastEvent(message);
|
|
|
|
const eventHandlers = handlers.get(message.event);
|
|
if (eventHandlers) {
|
|
eventHandlers.forEach(handler => handler(message.data));
|
|
}
|
|
} catch (e) {
|
|
console.error('Parse error:', e);
|
|
}
|
|
};
|
|
|
|
eventSource.onerror = () => {
|
|
setIsConnected(false);
|
|
};
|
|
|
|
return () => eventSource.close();
|
|
}, [handlers]);
|
|
|
|
const subscribe = <T = any>(event: RealtimeEvent, handler: (data: T) => void) => {
|
|
setHandlers(prev => {
|
|
const newHandlers = new Map(prev);
|
|
if (!newHandlers.has(event)) {
|
|
newHandlers.set(event, new Set());
|
|
}
|
|
newHandlers.get(event)!.add(handler);
|
|
return newHandlers;
|
|
});
|
|
|
|
return () => {
|
|
setHandlers(prev => {
|
|
const newHandlers = new Map(prev);
|
|
newHandlers.get(event)?.delete(handler);
|
|
return newHandlers;
|
|
});
|
|
};
|
|
};
|
|
|
|
return (
|
|
<RealtimeContext.Provider value={{ isConnected, subscribe, lastEvent }}>
|
|
{children}
|
|
</RealtimeContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useRealtimeContext() {
|
|
const context = useContext(RealtimeContext);
|
|
if (!context) {
|
|
throw new Error('useRealtimeContext must be used within RealtimeProvider');
|
|
}
|
|
return context;
|
|
}
|