Files
ProjectE/apps/web/components/realtime-provider.tsx
T
mbatchelder 8f55626e03 refactor: migrate to monorepo structure with Docker, PocketBase, and e2e tests
- Reorganize into apps/, packages/, docs/, e2e/, pocketbase/ directories
- Add Dockerfiles for web, worker, and PocketBase services
- Add docker-compose.yml for local orchestration
- Add turbo.json for monorepo task management
- Add Playwright e2e test infrastructure
- Add PocketBase backend with migrations
- Remove Vite/Next.js/ESLint/PostCSS config files
- Update package.json with workspace dependencies
- Add .env.example and .dockerignore
2026-07-16 06:19:58 -04:00

95 lines
2.5 KiB
TypeScript

'use client';
import {
createContext,
useContext,
useCallback,
useState,
useRef,
type ReactNode,
} from 'react';
import { useRealtime, type RealtimeEvent } from '@/hooks/use-realtime';
type EventCallback = (event: RealtimeEvent) => void;
interface RealtimeContextType {
connected: boolean;
error: string | null;
reconnect: () => void;
disconnect: () => void;
subscribe: (
collections: string[],
callback: EventCallback
) => () => void;
}
const RealtimeContext = createContext<RealtimeContextType | null>(null);
export function RealtimeProvider({ children }: { children: ReactNode }) {
const subscribersRef = useRef<Map<string, Set<EventCallback>>>(new Map());
const [, forceRender] = useState(0);
const handleEvent = useCallback((event: RealtimeEvent) => {
const collection = event.collection || '*';
const collectionSubs = subscribersRef.current.get(collection);
const globalSubs = subscribersRef.current.get('*');
if (collectionSubs) {
collectionSubs.forEach((cb) => cb(event));
}
if (globalSubs) {
globalSubs.forEach((cb) => cb(event));
}
}, []);
const { connected, error, reconnect, disconnect } = useRealtime({
onEvent: handleEvent,
});
const subscribe = useCallback(
(collections: string[], callback: EventCallback) => {
for (const collection of collections) {
if (!subscribersRef.current.has(collection)) {
subscribersRef.current.set(collection, new Set());
}
subscribersRef.current.get(collection)!.add(callback);
}
// Also register as a global subscriber
if (!subscribersRef.current.has('*')) {
subscribersRef.current.set('*', new Set());
}
subscribersRef.current.get('*')!.add(callback);
forceRender((n) => n + 1);
// Return unsubscribe function
return () => {
for (const collection of collections) {
subscribersRef.current.get(collection)?.delete(callback);
}
subscribersRef.current.get('*')?.delete(callback);
forceRender((n) => n + 1);
};
},
[]
);
return (
<RealtimeContext.Provider
value={{ connected, error, reconnect, disconnect, subscribe }}
>
{children}
</RealtimeContext.Provider>
);
}
export function useRealtimeContext() {
const context = useContext(RealtimeContext);
if (!context) {
throw new Error(
'useRealtimeContext must be used within a RealtimeProvider'
);
}
return context;
}