'use client'; import { createContext, useCallback, useContext, useRef } from 'react'; type EmptyPayload = NonNullable; // Base event types export interface BaseAppEventTypes { 'user.signedIn': { userId: string }; 'user.signedUp': { method: `magiclink` | `password` }; 'user.updated': EmptyPayload; 'checkout.started': { planId: string; account?: string }; // Add more base event types here } export type ConsumerProvidedEventTypes = EmptyPayload; // Helper type for extending event types export type ExtendedAppEventTypes< T extends ConsumerProvidedEventTypes = ConsumerProvidedEventTypes, > = BaseAppEventTypes & T; // Generic type for the entire module export type AppEventType = keyof ExtendedAppEventTypes; export type AppEvent< T extends ConsumerProvidedEventTypes = ConsumerProvidedEventTypes, K extends AppEventType = AppEventType, > = { type: K; payload: ExtendedAppEventTypes[K]; }; export type EventCallback< T extends ConsumerProvidedEventTypes, K extends AppEventType = AppEventType, > = (event: AppEvent) => void; interface InternalAppEventsContextType< T extends ConsumerProvidedEventTypes = ConsumerProvidedEventTypes, K extends AppEventType = AppEventType, > { emit: (event: AppEvent) => void; on: (eventType: K, callback: EventCallback) => void; off: (eventType: K, callback: EventCallback) => void; } interface AppEventsContextType { emit: >(event: AppEvent) => void; on: >( eventType: K, callback: EventCallback, ) => void; off: >( eventType: K, callback: EventCallback, ) => void; } const AppEventsContext = createContext( null, ); export function AppEventsProvider< T extends ConsumerProvidedEventTypes = ConsumerProvidedEventTypes, K extends AppEventType = AppEventType, >({ children }: React.PropsWithChildren) { const listeners = useRef[]>>( {} as Record[]>, ); const emit = useCallback( (event: AppEvent) => { const eventListeners = listeners.current[event.type] ?? []; eventListeners.forEach((callback) => callback(event)); }, [listeners], ); const on = useCallback((eventType: K, callback: EventCallback) => { listeners.current = { ...listeners.current, [eventType]: [...(listeners.current[eventType] ?? []), callback], }; }, []) as AppEventsContextType['on']; const off = useCallback((eventType: K, callback: EventCallback) => { listeners.current = { ...listeners.current, [eventType]: (listeners.current[eventType] ?? []).filter( (cb) => cb !== callback, ), }; }, []) as AppEventsContextType['off']; return ( {children} ); } export function useAppEvents< T extends ConsumerProvidedEventTypes = ConsumerProvidedEventTypes, >(): AppEventsContextType { const context = useContext(AppEventsContext); if (!context) { throw new Error('useAppEvents must be used within an AppEventsProvider'); } return context as AppEventsContextType; }