@zonetrix/shared 2.4.2 → 2.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,3 +1,5 @@
1
+ import { Database } from 'firebase/database';
2
+
1
3
  /**
2
4
  * Centralized type definitions for the Seat Map Studio packages
3
5
  */
@@ -18,6 +20,7 @@ interface SeatData {
18
20
  price?: number;
19
21
  seatNumber?: string;
20
22
  floorId?: string;
23
+ size?: number;
21
24
  }
22
25
  /**
23
26
  * Section configuration for creating seat groups
@@ -129,6 +132,7 @@ interface SerializedSeat {
129
132
  seatNumber?: string;
130
133
  price?: number;
131
134
  floorId?: string;
135
+ size?: number;
132
136
  }
133
137
  /**
134
138
  * Serialized section for JSON export/import
@@ -510,4 +514,224 @@ declare function deriveSeatArraysFromStates(states: FirebaseSeatStates, currentU
510
514
  */
511
515
  declare function createIndexUpdates(seatMapId: string, eventId: number, subEventId: number): Record<string, boolean>;
512
516
 
513
- export { type AlignmentGuide, type BookingSelection, type CanvasConfig, type CanvasState, type ColorSettings, DEFAULT_COLORS, type EditorMode, type FirebaseCanvasConfig, type FirebaseConfigResult, type FirebaseHookOptions, type FirebaseIndexEntry, FirebasePaths, type FirebasePosition, type FirebaseSeat, type FirebaseSeatMap, type FirebaseSeatMapConfig, type FirebaseSeatMapMeta, type FirebaseSeatState, type FirebaseSeatStateEntry, type FirebaseSeatStateValue, type FirebaseSeatStates, type FirebaseSeatStatesResult, type FirebaseStage, type ObjectConfig, type ObjectType, type RowPricing, type SeatData, type SeatMapConfig, type SeatMapMetadata, type SeatShape, type SeatState, type SectionConfig, type SerializedSeat, type SerializedSection, type SerializedStage, type StageConfig, type Tool, type ValidationResult, applySeatStateOverrides, calculateAvailableSeats, calculateCapacity, calculateSeatPrice, cloneConfig, createDefaultConfig, createIndexUpdates, decodeSeatId, deriveSeatArraysFromStates, downloadConfigAsFile, encodeSeatId, exportConfigAsJSON, extractSeatStates, formatDate, fromFirebaseSeatMap, fromFirebaseState, generateId, getSelectedSeats, importConfigFromJSON, toFirebaseSeatMap, toFirebaseState, updateConfigTimestamp, validateSeatMapConfig };
517
+ /**
518
+ * Firebase client singleton for the viewer package
519
+ * This allows the host application to initialize Firebase once
520
+ * and the viewer to use the same instance
521
+ */
522
+
523
+ /**
524
+ * Initialize the Firebase database instance for the viewer
525
+ * This should be called by the host application after Firebase is initialized
526
+ *
527
+ * @example
528
+ * ```tsx
529
+ * import { initializeApp } from 'firebase/app';
530
+ * import { getDatabase } from 'firebase/database';
531
+ * import { initializeFirebaseForViewer } from '@zonetrix/viewer';
532
+ *
533
+ * const app = initializeApp(firebaseConfig);
534
+ * const db = getDatabase(app);
535
+ * initializeFirebaseForViewer(db);
536
+ * ```
537
+ */
538
+ declare function initializeFirebaseForViewer(database: Database): void;
539
+ /**
540
+ * Get the Firebase database instance
541
+ * @throws Error if Firebase hasn't been initialized
542
+ */
543
+ declare function getFirebaseDatabase(): Database;
544
+ /**
545
+ * Check if Firebase has been initialized for the viewer
546
+ */
547
+ declare function isFirebaseInitialized(): boolean;
548
+ /**
549
+ * Clear the Firebase database instance (useful for testing)
550
+ */
551
+ declare function clearFirebaseInstance(): void;
552
+
553
+ /**
554
+ * Hook for real-time seat state updates from Firebase
555
+ * Uses useSyncExternalStore for stable snapshot handling
556
+ * Supports user-aware seat state derivation
557
+ */
558
+
559
+ interface UseFirebaseSeatStatesOptions {
560
+ /** The seat map ID to subscribe to */
561
+ seatMapId: string | null;
562
+ /** Current user ID for user-aware state derivation */
563
+ currentUserId?: string;
564
+ /** Whether the subscription is enabled (default: true) */
565
+ enabled?: boolean;
566
+ /** Callback when states change */
567
+ onStateChange?: (states: FirebaseSeatStates) => void;
568
+ /** Callback on error */
569
+ onError?: (error: Error) => void;
570
+ }
571
+ interface UseFirebaseSeatStatesResult {
572
+ /** Current seat states map */
573
+ states: FirebaseSeatStates | null;
574
+ /** Whether initial load is in progress */
575
+ loading: boolean;
576
+ /** Any error that occurred */
577
+ error: Error | null;
578
+ /** Timestamp of last update */
579
+ lastUpdated: number | null;
580
+ /** Seats reserved by current user (show as selected) */
581
+ myReservedSeats: string[];
582
+ /** Seats reserved by other users (show as reserved) */
583
+ otherReservedSeats: string[];
584
+ /** Seats unavailable for everyone */
585
+ unavailableSeats: string[];
586
+ /** @deprecated Use otherReservedSeats instead */
587
+ reservedSeats: string[];
588
+ }
589
+ /**
590
+ * Subscribe to real-time seat state updates from Firebase
591
+ * Uses useSyncExternalStore for stable snapshot handling
592
+ *
593
+ * @example
594
+ * ```tsx
595
+ * const { myReservedSeats, otherReservedSeats, unavailableSeats, loading } = useFirebaseSeatStates({
596
+ * seatMapId: '123',
597
+ * currentUserId: 'user-abc',
598
+ * });
599
+ *
600
+ * return (
601
+ * <SeatMapViewer
602
+ * config={config}
603
+ * myReservedSeats={myReservedSeats}
604
+ * reservedSeats={otherReservedSeats}
605
+ * unavailableSeats={unavailableSeats}
606
+ * />
607
+ * );
608
+ * ```
609
+ */
610
+ declare function useFirebaseSeatStates(options: UseFirebaseSeatStatesOptions): UseFirebaseSeatStatesResult;
611
+
612
+ /**
613
+ * Hook for loading seat map configuration from Firebase
614
+ * Optionally subscribes to design changes
615
+ */
616
+
617
+ interface UseFirebaseConfigOptions {
618
+ /** The seat map ID to load */
619
+ seatMapId: string | null;
620
+ /** Whether loading is enabled (default: true) */
621
+ enabled?: boolean;
622
+ /** Subscribe to design changes in real-time (default: false) */
623
+ subscribeToChanges?: boolean;
624
+ /** Callback when config loads or changes */
625
+ onConfigLoad?: (config: SeatMapConfig) => void;
626
+ /** Callback on error */
627
+ onError?: (error: Error) => void;
628
+ }
629
+ interface UseFirebaseConfigResult {
630
+ /** The loaded configuration */
631
+ config: SeatMapConfig | null;
632
+ /** Whether loading is in progress */
633
+ loading: boolean;
634
+ /** Any error that occurred */
635
+ error: Error | null;
636
+ /** Manually refetch the config */
637
+ refetch: () => Promise<void>;
638
+ }
639
+ /**
640
+ * Load seat map configuration from Firebase
641
+ *
642
+ * @example
643
+ * ```tsx
644
+ * // One-time load
645
+ * const { config, loading, error } = useFirebaseConfig({
646
+ * seatMapId: '123',
647
+ * });
648
+ *
649
+ * // With real-time design updates (for admin/editor preview)
650
+ * const { config } = useFirebaseConfig({
651
+ * seatMapId: '123',
652
+ * subscribeToChanges: true,
653
+ * });
654
+ * ```
655
+ */
656
+ declare function useFirebaseConfig(options: UseFirebaseConfigOptions): UseFirebaseConfigResult;
657
+
658
+ /**
659
+ * Combined hook for loading seat map config and subscribing to real-time state updates
660
+ * This is the recommended hook for most use cases
661
+ * Supports user-aware seat state derivation
662
+ */
663
+
664
+ interface UseRealtimeSeatMapOptions {
665
+ /** The seat map ID to load and subscribe to */
666
+ seatMapId: string | null;
667
+ /** Current user ID for user-aware state derivation */
668
+ userId?: string;
669
+ /** Whether the hook is enabled (default: true) */
670
+ enabled?: boolean;
671
+ /** Subscribe to design changes in real-time (default: false) */
672
+ subscribeToDesignChanges?: boolean;
673
+ /** Callback when config loads */
674
+ onConfigLoad?: (config: SeatMapConfig) => void;
675
+ /** Callback when seat states change */
676
+ onStateChange?: (states: FirebaseSeatStates) => void;
677
+ /** Callback on any error */
678
+ onError?: (error: Error) => void;
679
+ }
680
+ interface UseRealtimeSeatMapResult {
681
+ /** The seat map configuration */
682
+ config: SeatMapConfig | null;
683
+ /** Whether initial loading is in progress */
684
+ loading: boolean;
685
+ /** Any error that occurred */
686
+ error: Error | null;
687
+ /** Seats reserved by current user (show as selected) */
688
+ myReservedSeats: string[];
689
+ /** Seats reserved by other users (show as reserved) */
690
+ otherReservedSeats: string[];
691
+ /** Seats unavailable for everyone */
692
+ unavailableSeats: string[];
693
+ /** @deprecated Use otherReservedSeats instead */
694
+ reservedSeats: string[];
695
+ /** Raw seat states map */
696
+ seatStates: FirebaseSeatStates | null;
697
+ /** Timestamp of last state update */
698
+ lastUpdated: number | null;
699
+ /** Manually refetch the config */
700
+ refetch: () => Promise<void>;
701
+ }
702
+ /**
703
+ * Combined hook for loading config and subscribing to real-time seat states
704
+ *
705
+ * @example
706
+ * ```tsx
707
+ * import { useRealtimeSeatMap, SeatMapViewer } from '@zonetrix/viewer';
708
+ *
709
+ * function BookingPage({ seatMapId, userId }) {
710
+ * const {
711
+ * config,
712
+ * myReservedSeats,
713
+ * otherReservedSeats,
714
+ * unavailableSeats,
715
+ * loading,
716
+ * error
717
+ * } = useRealtimeSeatMap({ seatMapId, userId });
718
+ *
719
+ * if (loading) return <LoadingSpinner />;
720
+ * if (error) return <ErrorMessage error={error} />;
721
+ * if (!config) return null;
722
+ *
723
+ * return (
724
+ * <SeatMapViewer
725
+ * config={config}
726
+ * myReservedSeats={myReservedSeats}
727
+ * reservedSeats={otherReservedSeats}
728
+ * unavailableSeats={unavailableSeats}
729
+ * onSeatSelect={handleSeatSelect}
730
+ * />
731
+ * );
732
+ * }
733
+ * ```
734
+ */
735
+ declare function useRealtimeSeatMap(options: UseRealtimeSeatMapOptions): UseRealtimeSeatMapResult;
736
+
737
+ export { type AlignmentGuide, type BookingSelection, type CanvasConfig, type CanvasState, type ColorSettings, DEFAULT_COLORS, type EditorMode, type FirebaseCanvasConfig, type FirebaseConfigResult, type FirebaseHookOptions, type FirebaseIndexEntry, FirebasePaths, type FirebasePosition, type FirebaseSeat, type FirebaseSeatMap, type FirebaseSeatMapConfig, type FirebaseSeatMapMeta, type FirebaseSeatState, type FirebaseSeatStateEntry, type FirebaseSeatStateValue, type FirebaseSeatStates, type FirebaseSeatStatesResult, type FirebaseStage, type ObjectConfig, type ObjectType, type RowPricing, type SeatData, type SeatMapConfig, type SeatMapMetadata, type SeatShape, type SeatState, type SectionConfig, type SerializedSeat, type SerializedSection, type SerializedStage, type StageConfig, type Tool, type UseFirebaseConfigOptions, type UseFirebaseConfigResult, type UseFirebaseSeatStatesOptions, type UseFirebaseSeatStatesResult, type UseRealtimeSeatMapOptions, type UseRealtimeSeatMapResult, type ValidationResult, applySeatStateOverrides, calculateAvailableSeats, calculateCapacity, calculateSeatPrice, clearFirebaseInstance, cloneConfig, createDefaultConfig, createIndexUpdates, decodeSeatId, deriveSeatArraysFromStates, downloadConfigAsFile, encodeSeatId, exportConfigAsJSON, extractSeatStates, formatDate, fromFirebaseSeatMap, fromFirebaseState, generateId, getFirebaseDatabase, getSelectedSeats, importConfigFromJSON, initializeFirebaseForViewer, isFirebaseInitialized, toFirebaseSeatMap, toFirebaseState, updateConfigTimestamp, useFirebaseConfig, useFirebaseSeatStates, useRealtimeSeatMap, validateSeatMapConfig };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { Database } from 'firebase/database';
2
+
1
3
  /**
2
4
  * Centralized type definitions for the Seat Map Studio packages
3
5
  */
@@ -18,6 +20,7 @@ interface SeatData {
18
20
  price?: number;
19
21
  seatNumber?: string;
20
22
  floorId?: string;
23
+ size?: number;
21
24
  }
22
25
  /**
23
26
  * Section configuration for creating seat groups
@@ -129,6 +132,7 @@ interface SerializedSeat {
129
132
  seatNumber?: string;
130
133
  price?: number;
131
134
  floorId?: string;
135
+ size?: number;
132
136
  }
133
137
  /**
134
138
  * Serialized section for JSON export/import
@@ -510,4 +514,224 @@ declare function deriveSeatArraysFromStates(states: FirebaseSeatStates, currentU
510
514
  */
511
515
  declare function createIndexUpdates(seatMapId: string, eventId: number, subEventId: number): Record<string, boolean>;
512
516
 
513
- export { type AlignmentGuide, type BookingSelection, type CanvasConfig, type CanvasState, type ColorSettings, DEFAULT_COLORS, type EditorMode, type FirebaseCanvasConfig, type FirebaseConfigResult, type FirebaseHookOptions, type FirebaseIndexEntry, FirebasePaths, type FirebasePosition, type FirebaseSeat, type FirebaseSeatMap, type FirebaseSeatMapConfig, type FirebaseSeatMapMeta, type FirebaseSeatState, type FirebaseSeatStateEntry, type FirebaseSeatStateValue, type FirebaseSeatStates, type FirebaseSeatStatesResult, type FirebaseStage, type ObjectConfig, type ObjectType, type RowPricing, type SeatData, type SeatMapConfig, type SeatMapMetadata, type SeatShape, type SeatState, type SectionConfig, type SerializedSeat, type SerializedSection, type SerializedStage, type StageConfig, type Tool, type ValidationResult, applySeatStateOverrides, calculateAvailableSeats, calculateCapacity, calculateSeatPrice, cloneConfig, createDefaultConfig, createIndexUpdates, decodeSeatId, deriveSeatArraysFromStates, downloadConfigAsFile, encodeSeatId, exportConfigAsJSON, extractSeatStates, formatDate, fromFirebaseSeatMap, fromFirebaseState, generateId, getSelectedSeats, importConfigFromJSON, toFirebaseSeatMap, toFirebaseState, updateConfigTimestamp, validateSeatMapConfig };
517
+ /**
518
+ * Firebase client singleton for the viewer package
519
+ * This allows the host application to initialize Firebase once
520
+ * and the viewer to use the same instance
521
+ */
522
+
523
+ /**
524
+ * Initialize the Firebase database instance for the viewer
525
+ * This should be called by the host application after Firebase is initialized
526
+ *
527
+ * @example
528
+ * ```tsx
529
+ * import { initializeApp } from 'firebase/app';
530
+ * import { getDatabase } from 'firebase/database';
531
+ * import { initializeFirebaseForViewer } from '@zonetrix/viewer';
532
+ *
533
+ * const app = initializeApp(firebaseConfig);
534
+ * const db = getDatabase(app);
535
+ * initializeFirebaseForViewer(db);
536
+ * ```
537
+ */
538
+ declare function initializeFirebaseForViewer(database: Database): void;
539
+ /**
540
+ * Get the Firebase database instance
541
+ * @throws Error if Firebase hasn't been initialized
542
+ */
543
+ declare function getFirebaseDatabase(): Database;
544
+ /**
545
+ * Check if Firebase has been initialized for the viewer
546
+ */
547
+ declare function isFirebaseInitialized(): boolean;
548
+ /**
549
+ * Clear the Firebase database instance (useful for testing)
550
+ */
551
+ declare function clearFirebaseInstance(): void;
552
+
553
+ /**
554
+ * Hook for real-time seat state updates from Firebase
555
+ * Uses useSyncExternalStore for stable snapshot handling
556
+ * Supports user-aware seat state derivation
557
+ */
558
+
559
+ interface UseFirebaseSeatStatesOptions {
560
+ /** The seat map ID to subscribe to */
561
+ seatMapId: string | null;
562
+ /** Current user ID for user-aware state derivation */
563
+ currentUserId?: string;
564
+ /** Whether the subscription is enabled (default: true) */
565
+ enabled?: boolean;
566
+ /** Callback when states change */
567
+ onStateChange?: (states: FirebaseSeatStates) => void;
568
+ /** Callback on error */
569
+ onError?: (error: Error) => void;
570
+ }
571
+ interface UseFirebaseSeatStatesResult {
572
+ /** Current seat states map */
573
+ states: FirebaseSeatStates | null;
574
+ /** Whether initial load is in progress */
575
+ loading: boolean;
576
+ /** Any error that occurred */
577
+ error: Error | null;
578
+ /** Timestamp of last update */
579
+ lastUpdated: number | null;
580
+ /** Seats reserved by current user (show as selected) */
581
+ myReservedSeats: string[];
582
+ /** Seats reserved by other users (show as reserved) */
583
+ otherReservedSeats: string[];
584
+ /** Seats unavailable for everyone */
585
+ unavailableSeats: string[];
586
+ /** @deprecated Use otherReservedSeats instead */
587
+ reservedSeats: string[];
588
+ }
589
+ /**
590
+ * Subscribe to real-time seat state updates from Firebase
591
+ * Uses useSyncExternalStore for stable snapshot handling
592
+ *
593
+ * @example
594
+ * ```tsx
595
+ * const { myReservedSeats, otherReservedSeats, unavailableSeats, loading } = useFirebaseSeatStates({
596
+ * seatMapId: '123',
597
+ * currentUserId: 'user-abc',
598
+ * });
599
+ *
600
+ * return (
601
+ * <SeatMapViewer
602
+ * config={config}
603
+ * myReservedSeats={myReservedSeats}
604
+ * reservedSeats={otherReservedSeats}
605
+ * unavailableSeats={unavailableSeats}
606
+ * />
607
+ * );
608
+ * ```
609
+ */
610
+ declare function useFirebaseSeatStates(options: UseFirebaseSeatStatesOptions): UseFirebaseSeatStatesResult;
611
+
612
+ /**
613
+ * Hook for loading seat map configuration from Firebase
614
+ * Optionally subscribes to design changes
615
+ */
616
+
617
+ interface UseFirebaseConfigOptions {
618
+ /** The seat map ID to load */
619
+ seatMapId: string | null;
620
+ /** Whether loading is enabled (default: true) */
621
+ enabled?: boolean;
622
+ /** Subscribe to design changes in real-time (default: false) */
623
+ subscribeToChanges?: boolean;
624
+ /** Callback when config loads or changes */
625
+ onConfigLoad?: (config: SeatMapConfig) => void;
626
+ /** Callback on error */
627
+ onError?: (error: Error) => void;
628
+ }
629
+ interface UseFirebaseConfigResult {
630
+ /** The loaded configuration */
631
+ config: SeatMapConfig | null;
632
+ /** Whether loading is in progress */
633
+ loading: boolean;
634
+ /** Any error that occurred */
635
+ error: Error | null;
636
+ /** Manually refetch the config */
637
+ refetch: () => Promise<void>;
638
+ }
639
+ /**
640
+ * Load seat map configuration from Firebase
641
+ *
642
+ * @example
643
+ * ```tsx
644
+ * // One-time load
645
+ * const { config, loading, error } = useFirebaseConfig({
646
+ * seatMapId: '123',
647
+ * });
648
+ *
649
+ * // With real-time design updates (for admin/editor preview)
650
+ * const { config } = useFirebaseConfig({
651
+ * seatMapId: '123',
652
+ * subscribeToChanges: true,
653
+ * });
654
+ * ```
655
+ */
656
+ declare function useFirebaseConfig(options: UseFirebaseConfigOptions): UseFirebaseConfigResult;
657
+
658
+ /**
659
+ * Combined hook for loading seat map config and subscribing to real-time state updates
660
+ * This is the recommended hook for most use cases
661
+ * Supports user-aware seat state derivation
662
+ */
663
+
664
+ interface UseRealtimeSeatMapOptions {
665
+ /** The seat map ID to load and subscribe to */
666
+ seatMapId: string | null;
667
+ /** Current user ID for user-aware state derivation */
668
+ userId?: string;
669
+ /** Whether the hook is enabled (default: true) */
670
+ enabled?: boolean;
671
+ /** Subscribe to design changes in real-time (default: false) */
672
+ subscribeToDesignChanges?: boolean;
673
+ /** Callback when config loads */
674
+ onConfigLoad?: (config: SeatMapConfig) => void;
675
+ /** Callback when seat states change */
676
+ onStateChange?: (states: FirebaseSeatStates) => void;
677
+ /** Callback on any error */
678
+ onError?: (error: Error) => void;
679
+ }
680
+ interface UseRealtimeSeatMapResult {
681
+ /** The seat map configuration */
682
+ config: SeatMapConfig | null;
683
+ /** Whether initial loading is in progress */
684
+ loading: boolean;
685
+ /** Any error that occurred */
686
+ error: Error | null;
687
+ /** Seats reserved by current user (show as selected) */
688
+ myReservedSeats: string[];
689
+ /** Seats reserved by other users (show as reserved) */
690
+ otherReservedSeats: string[];
691
+ /** Seats unavailable for everyone */
692
+ unavailableSeats: string[];
693
+ /** @deprecated Use otherReservedSeats instead */
694
+ reservedSeats: string[];
695
+ /** Raw seat states map */
696
+ seatStates: FirebaseSeatStates | null;
697
+ /** Timestamp of last state update */
698
+ lastUpdated: number | null;
699
+ /** Manually refetch the config */
700
+ refetch: () => Promise<void>;
701
+ }
702
+ /**
703
+ * Combined hook for loading config and subscribing to real-time seat states
704
+ *
705
+ * @example
706
+ * ```tsx
707
+ * import { useRealtimeSeatMap, SeatMapViewer } from '@zonetrix/viewer';
708
+ *
709
+ * function BookingPage({ seatMapId, userId }) {
710
+ * const {
711
+ * config,
712
+ * myReservedSeats,
713
+ * otherReservedSeats,
714
+ * unavailableSeats,
715
+ * loading,
716
+ * error
717
+ * } = useRealtimeSeatMap({ seatMapId, userId });
718
+ *
719
+ * if (loading) return <LoadingSpinner />;
720
+ * if (error) return <ErrorMessage error={error} />;
721
+ * if (!config) return null;
722
+ *
723
+ * return (
724
+ * <SeatMapViewer
725
+ * config={config}
726
+ * myReservedSeats={myReservedSeats}
727
+ * reservedSeats={otherReservedSeats}
728
+ * unavailableSeats={unavailableSeats}
729
+ * onSeatSelect={handleSeatSelect}
730
+ * />
731
+ * );
732
+ * }
733
+ * ```
734
+ */
735
+ declare function useRealtimeSeatMap(options: UseRealtimeSeatMapOptions): UseRealtimeSeatMapResult;
736
+
737
+ export { type AlignmentGuide, type BookingSelection, type CanvasConfig, type CanvasState, type ColorSettings, DEFAULT_COLORS, type EditorMode, type FirebaseCanvasConfig, type FirebaseConfigResult, type FirebaseHookOptions, type FirebaseIndexEntry, FirebasePaths, type FirebasePosition, type FirebaseSeat, type FirebaseSeatMap, type FirebaseSeatMapConfig, type FirebaseSeatMapMeta, type FirebaseSeatState, type FirebaseSeatStateEntry, type FirebaseSeatStateValue, type FirebaseSeatStates, type FirebaseSeatStatesResult, type FirebaseStage, type ObjectConfig, type ObjectType, type RowPricing, type SeatData, type SeatMapConfig, type SeatMapMetadata, type SeatShape, type SeatState, type SectionConfig, type SerializedSeat, type SerializedSection, type SerializedStage, type StageConfig, type Tool, type UseFirebaseConfigOptions, type UseFirebaseConfigResult, type UseFirebaseSeatStatesOptions, type UseFirebaseSeatStatesResult, type UseRealtimeSeatMapOptions, type UseRealtimeSeatMapResult, type ValidationResult, applySeatStateOverrides, calculateAvailableSeats, calculateCapacity, calculateSeatPrice, clearFirebaseInstance, cloneConfig, createDefaultConfig, createIndexUpdates, decodeSeatId, deriveSeatArraysFromStates, downloadConfigAsFile, encodeSeatId, exportConfigAsJSON, extractSeatStates, formatDate, fromFirebaseSeatMap, fromFirebaseState, generateId, getFirebaseDatabase, getSelectedSeats, importConfigFromJSON, initializeFirebaseForViewer, isFirebaseInitialized, toFirebaseSeatMap, toFirebaseState, updateConfigTimestamp, useFirebaseConfig, useFirebaseSeatStates, useRealtimeSeatMap, validateSeatMapConfig };
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ __export(index_exports, {
26
26
  calculateAvailableSeats: () => calculateAvailableSeats,
27
27
  calculateCapacity: () => calculateCapacity,
28
28
  calculateSeatPrice: () => calculateSeatPrice,
29
+ clearFirebaseInstance: () => clearFirebaseInstance,
29
30
  cloneConfig: () => cloneConfig,
30
31
  createDefaultConfig: () => createDefaultConfig,
31
32
  createIndexUpdates: () => createIndexUpdates,
@@ -39,11 +40,17 @@ __export(index_exports, {
39
40
  fromFirebaseSeatMap: () => fromFirebaseSeatMap,
40
41
  fromFirebaseState: () => fromFirebaseState,
41
42
  generateId: () => generateId,
43
+ getFirebaseDatabase: () => getFirebaseDatabase,
42
44
  getSelectedSeats: () => getSelectedSeats,
43
45
  importConfigFromJSON: () => importConfigFromJSON,
46
+ initializeFirebaseForViewer: () => initializeFirebaseForViewer,
47
+ isFirebaseInitialized: () => isFirebaseInitialized,
44
48
  toFirebaseSeatMap: () => toFirebaseSeatMap,
45
49
  toFirebaseState: () => toFirebaseState,
46
50
  updateConfigTimestamp: () => updateConfigTimestamp,
51
+ useFirebaseConfig: () => useFirebaseConfig,
52
+ useFirebaseSeatStates: () => useFirebaseSeatStates,
53
+ useRealtimeSeatMap: () => useRealtimeSeatMap,
47
54
  validateSeatMapConfig: () => validateSeatMapConfig
48
55
  });
49
56
  module.exports = __toCommonJS(index_exports);
@@ -488,6 +495,335 @@ function createIndexUpdates(seatMapId, eventId, subEventId) {
488
495
  [`indexes/by_sub_event/${subEventId}/${seatMapId}`]: true
489
496
  };
490
497
  }
498
+
499
+ // src/firebase/client.ts
500
+ var firebaseDatabase = null;
501
+ function initializeFirebaseForViewer(database) {
502
+ firebaseDatabase = database;
503
+ }
504
+ function getFirebaseDatabase() {
505
+ if (!firebaseDatabase) {
506
+ throw new Error(
507
+ "Firebase database not initialized. Call initializeFirebaseForViewer(db) first."
508
+ );
509
+ }
510
+ return firebaseDatabase;
511
+ }
512
+ function isFirebaseInitialized() {
513
+ return firebaseDatabase !== null;
514
+ }
515
+ function clearFirebaseInstance() {
516
+ firebaseDatabase = null;
517
+ }
518
+
519
+ // src/firebase/hooks/useFirebaseSeatStates.ts
520
+ var import_react = require("react");
521
+ var import_database = require("firebase/database");
522
+ function arraysEqualUnordered(a, b) {
523
+ if (a.length !== b.length) return false;
524
+ const sortedA = [...a].sort();
525
+ const sortedB = [...b].sort();
526
+ return sortedA.every((val, idx) => val === sortedB[idx]);
527
+ }
528
+ function deriveUserAwareSeatArrays(statesData, currentUserId) {
529
+ const myReserved = [];
530
+ const otherReserved = [];
531
+ const unavailable = [];
532
+ Object.entries(statesData).forEach(([seatId, entry]) => {
533
+ if (!entry) return;
534
+ if (typeof entry === "object" && entry.state) {
535
+ if (entry.state === "unavailable") {
536
+ unavailable.push(seatId);
537
+ } else if (entry.state === "reserved") {
538
+ if (currentUserId && entry.userId === currentUserId) {
539
+ myReserved.push(seatId);
540
+ } else {
541
+ otherReserved.push(seatId);
542
+ }
543
+ }
544
+ }
545
+ });
546
+ return {
547
+ myReserved: myReserved.sort(),
548
+ otherReserved: otherReserved.sort(),
549
+ unavailable: unavailable.sort()
550
+ };
551
+ }
552
+ var INITIAL_SNAPSHOT = {
553
+ states: null,
554
+ loading: true,
555
+ error: null,
556
+ lastUpdated: null,
557
+ myReservedSeats: [],
558
+ otherReservedSeats: [],
559
+ unavailableSeats: []
560
+ };
561
+ function useFirebaseSeatStates(options) {
562
+ const {
563
+ seatMapId,
564
+ currentUserId,
565
+ enabled = true,
566
+ onStateChange,
567
+ onError
568
+ } = options;
569
+ const snapshotRef = (0, import_react.useRef)({ ...INITIAL_SNAPSHOT });
570
+ const onStateChangeRef = (0, import_react.useRef)(onStateChange);
571
+ const onErrorRef = (0, import_react.useRef)(onError);
572
+ const currentUserIdRef = (0, import_react.useRef)(currentUserId);
573
+ onStateChangeRef.current = onStateChange;
574
+ onErrorRef.current = onError;
575
+ currentUserIdRef.current = currentUserId;
576
+ const subscribe = (0, import_react.useCallback)(
577
+ (callback) => {
578
+ if (!enabled || !seatMapId) {
579
+ if (snapshotRef.current.loading !== false || snapshotRef.current.states !== null) {
580
+ snapshotRef.current = { ...INITIAL_SNAPSHOT, loading: false };
581
+ callback();
582
+ }
583
+ return () => {
584
+ };
585
+ }
586
+ if (!isFirebaseInitialized()) {
587
+ const errorMsg = "Firebase not initialized. Call initializeFirebaseForViewer first.";
588
+ if (snapshotRef.current.error?.message !== errorMsg) {
589
+ snapshotRef.current = {
590
+ ...INITIAL_SNAPSHOT,
591
+ loading: false,
592
+ error: new Error(errorMsg)
593
+ };
594
+ callback();
595
+ }
596
+ return () => {
597
+ };
598
+ }
599
+ const db = getFirebaseDatabase();
600
+ const statesRef = (0, import_database.ref)(db, `seatmaps/${seatMapId}/seat_states`);
601
+ console.log("\u{1F525} Firebase Listener - Subscribing to:", `seatmaps/${seatMapId}/seat_states`);
602
+ const handleValue = (dataSnapshot) => {
603
+ const data = dataSnapshot.val();
604
+ const statesData = data || {};
605
+ const derived = deriveUserAwareSeatArrays(
606
+ statesData,
607
+ currentUserIdRef.current
608
+ );
609
+ console.log("\u{1F4E1} Firebase Event Received:", {
610
+ path: `seatmaps/${seatMapId}/seat_states`,
611
+ dataSnapshot: dataSnapshot.exists(),
612
+ rawData: data,
613
+ statesCount: Object.keys(statesData).length,
614
+ derived: {
615
+ myReserved: derived.myReserved,
616
+ otherReserved: derived.otherReserved,
617
+ unavailable: derived.unavailable
618
+ }
619
+ });
620
+ const current = snapshotRef.current;
621
+ const hasChanged = current.loading || !arraysEqualUnordered(current.myReservedSeats, derived.myReserved) || !arraysEqualUnordered(
622
+ current.otherReservedSeats,
623
+ derived.otherReserved
624
+ ) || !arraysEqualUnordered(current.unavailableSeats, derived.unavailable);
625
+ console.log("\u{1F504} Firebase Data Changed?", {
626
+ hasChanged,
627
+ currentOtherReserved: current.otherReservedSeats,
628
+ newOtherReserved: derived.otherReserved
629
+ });
630
+ if (hasChanged) {
631
+ console.log("\u2705 Updating React state with new Firebase data");
632
+ snapshotRef.current = {
633
+ states: statesData,
634
+ loading: false,
635
+ error: null,
636
+ lastUpdated: Date.now(),
637
+ myReservedSeats: derived.myReserved,
638
+ otherReservedSeats: derived.otherReserved,
639
+ unavailableSeats: derived.unavailable
640
+ };
641
+ onStateChangeRef.current?.(statesData);
642
+ callback();
643
+ } else {
644
+ console.log("\u23ED\uFE0F No change detected, skipping update");
645
+ }
646
+ };
647
+ const handleError = (err) => {
648
+ snapshotRef.current = {
649
+ ...snapshotRef.current,
650
+ loading: false,
651
+ error: err
652
+ };
653
+ onErrorRef.current?.(err);
654
+ callback();
655
+ };
656
+ (0, import_database.onValue)(statesRef, handleValue, handleError);
657
+ return () => {
658
+ console.log("\u{1F525} Firebase Listener - Unsubscribing from:", `seatmaps/${seatMapId}/seat_states`);
659
+ (0, import_database.off)(statesRef);
660
+ };
661
+ },
662
+ [seatMapId, enabled]
663
+ // Don't include currentUserId - we use currentUserIdRef.current which is always up-to-date
664
+ );
665
+ const getSnapshot = (0, import_react.useCallback)(() => snapshotRef.current, []);
666
+ const getServerSnapshot = (0, import_react.useCallback)(() => INITIAL_SNAPSHOT, []);
667
+ const snapshot = (0, import_react.useSyncExternalStore)(
668
+ subscribe,
669
+ getSnapshot,
670
+ getServerSnapshot
671
+ );
672
+ return {
673
+ states: snapshot.states,
674
+ loading: snapshot.loading,
675
+ error: snapshot.error,
676
+ lastUpdated: snapshot.lastUpdated,
677
+ myReservedSeats: snapshot.myReservedSeats,
678
+ otherReservedSeats: snapshot.otherReservedSeats,
679
+ unavailableSeats: snapshot.unavailableSeats,
680
+ // Legacy alias
681
+ reservedSeats: snapshot.otherReservedSeats
682
+ };
683
+ }
684
+
685
+ // src/firebase/hooks/useFirebaseConfig.ts
686
+ var import_react2 = require("react");
687
+ var import_database2 = require("firebase/database");
688
+ function useFirebaseConfig(options) {
689
+ const {
690
+ seatMapId,
691
+ enabled = true,
692
+ subscribeToChanges = false,
693
+ onConfigLoad,
694
+ onError
695
+ } = options;
696
+ const [config, setConfig] = (0, import_react2.useState)(null);
697
+ const [loading, setLoading] = (0, import_react2.useState)(true);
698
+ const [error, setError] = (0, import_react2.useState)(null);
699
+ const onConfigLoadRef = (0, import_react2.useRef)(onConfigLoad);
700
+ const onErrorRef = (0, import_react2.useRef)(onError);
701
+ onConfigLoadRef.current = onConfigLoad;
702
+ onErrorRef.current = onError;
703
+ const fetchConfig = (0, import_react2.useCallback)(async () => {
704
+ if (!seatMapId) return;
705
+ if (!isFirebaseInitialized()) {
706
+ setError(
707
+ new Error(
708
+ "Firebase not initialized. Call initializeFirebaseForViewer first."
709
+ )
710
+ );
711
+ setLoading(false);
712
+ return;
713
+ }
714
+ const db = getFirebaseDatabase();
715
+ const seatmapRef = (0, import_database2.ref)(db, `seatmaps/${seatMapId}`);
716
+ try {
717
+ setLoading(true);
718
+ setError(null);
719
+ const snapshot = await (0, import_database2.get)(seatmapRef);
720
+ const data = snapshot.val();
721
+ if (data) {
722
+ const converted = fromFirebaseSeatMap(data);
723
+ setConfig(converted);
724
+ onConfigLoadRef.current?.(converted);
725
+ } else {
726
+ setError(new Error(`Seat map ${seatMapId} not found in Firebase`));
727
+ }
728
+ } catch (err) {
729
+ const error2 = err instanceof Error ? err : new Error("Unknown error");
730
+ setError(error2);
731
+ onErrorRef.current?.(error2);
732
+ } finally {
733
+ setLoading(false);
734
+ }
735
+ }, [seatMapId]);
736
+ (0, import_react2.useEffect)(() => {
737
+ if (!enabled || !seatMapId) {
738
+ setLoading(false);
739
+ return;
740
+ }
741
+ fetchConfig();
742
+ if (subscribeToChanges && isFirebaseInitialized()) {
743
+ const db = getFirebaseDatabase();
744
+ const metaRef = (0, import_database2.ref)(db, `seatmaps/${seatMapId}/meta/updated_at`);
745
+ let isFirstSnapshot = true;
746
+ let lastSeenTimestamp = null;
747
+ const handleUpdate = (snapshot) => {
748
+ if (isFirstSnapshot) {
749
+ isFirstSnapshot = false;
750
+ lastSeenTimestamp = snapshot.val();
751
+ return;
752
+ }
753
+ const currentTimestamp = snapshot.val();
754
+ if (snapshot.exists() && currentTimestamp !== lastSeenTimestamp) {
755
+ lastSeenTimestamp = currentTimestamp;
756
+ fetchConfig();
757
+ }
758
+ };
759
+ (0, import_database2.onValue)(metaRef, handleUpdate);
760
+ return () => {
761
+ (0, import_database2.off)(metaRef);
762
+ };
763
+ }
764
+ }, [seatMapId, enabled, subscribeToChanges]);
765
+ return {
766
+ config,
767
+ loading,
768
+ error,
769
+ refetch: fetchConfig
770
+ };
771
+ }
772
+
773
+ // src/firebase/hooks/useRealtimeSeatMap.ts
774
+ function useRealtimeSeatMap(options) {
775
+ const {
776
+ seatMapId,
777
+ userId,
778
+ enabled = true,
779
+ subscribeToDesignChanges = false,
780
+ onConfigLoad,
781
+ onStateChange,
782
+ onError
783
+ } = options;
784
+ const {
785
+ config,
786
+ loading: configLoading,
787
+ error: configError,
788
+ refetch
789
+ } = useFirebaseConfig({
790
+ seatMapId,
791
+ enabled,
792
+ subscribeToChanges: subscribeToDesignChanges,
793
+ onConfigLoad,
794
+ onError
795
+ });
796
+ const {
797
+ states: seatStates,
798
+ loading: statesLoading,
799
+ error: statesError,
800
+ lastUpdated,
801
+ myReservedSeats,
802
+ otherReservedSeats,
803
+ unavailableSeats,
804
+ reservedSeats
805
+ } = useFirebaseSeatStates({
806
+ seatMapId,
807
+ currentUserId: userId,
808
+ enabled,
809
+ onStateChange,
810
+ onError
811
+ });
812
+ const loading = configLoading || statesLoading;
813
+ const error = configError || statesError;
814
+ return {
815
+ config,
816
+ loading,
817
+ error,
818
+ myReservedSeats,
819
+ otherReservedSeats,
820
+ unavailableSeats,
821
+ reservedSeats,
822
+ seatStates,
823
+ lastUpdated,
824
+ refetch
825
+ };
826
+ }
491
827
  // Annotate the CommonJS export names for ESM import in node:
492
828
  0 && (module.exports = {
493
829
  DEFAULT_COLORS,
@@ -496,6 +832,7 @@ function createIndexUpdates(seatMapId, eventId, subEventId) {
496
832
  calculateAvailableSeats,
497
833
  calculateCapacity,
498
834
  calculateSeatPrice,
835
+ clearFirebaseInstance,
499
836
  cloneConfig,
500
837
  createDefaultConfig,
501
838
  createIndexUpdates,
@@ -509,10 +846,16 @@ function createIndexUpdates(seatMapId, eventId, subEventId) {
509
846
  fromFirebaseSeatMap,
510
847
  fromFirebaseState,
511
848
  generateId,
849
+ getFirebaseDatabase,
512
850
  getSelectedSeats,
513
851
  importConfigFromJSON,
852
+ initializeFirebaseForViewer,
853
+ isFirebaseInitialized,
514
854
  toFirebaseSeatMap,
515
855
  toFirebaseState,
516
856
  updateConfigTimestamp,
857
+ useFirebaseConfig,
858
+ useFirebaseSeatStates,
859
+ useRealtimeSeatMap,
517
860
  validateSeatMapConfig
518
861
  });
package/dist/index.mjs CHANGED
@@ -438,6 +438,335 @@ function createIndexUpdates(seatMapId, eventId, subEventId) {
438
438
  [`indexes/by_sub_event/${subEventId}/${seatMapId}`]: true
439
439
  };
440
440
  }
441
+
442
+ // src/firebase/client.ts
443
+ var firebaseDatabase = null;
444
+ function initializeFirebaseForViewer(database) {
445
+ firebaseDatabase = database;
446
+ }
447
+ function getFirebaseDatabase() {
448
+ if (!firebaseDatabase) {
449
+ throw new Error(
450
+ "Firebase database not initialized. Call initializeFirebaseForViewer(db) first."
451
+ );
452
+ }
453
+ return firebaseDatabase;
454
+ }
455
+ function isFirebaseInitialized() {
456
+ return firebaseDatabase !== null;
457
+ }
458
+ function clearFirebaseInstance() {
459
+ firebaseDatabase = null;
460
+ }
461
+
462
+ // src/firebase/hooks/useFirebaseSeatStates.ts
463
+ import { useSyncExternalStore, useCallback, useRef } from "react";
464
+ import { ref, onValue, off } from "firebase/database";
465
+ function arraysEqualUnordered(a, b) {
466
+ if (a.length !== b.length) return false;
467
+ const sortedA = [...a].sort();
468
+ const sortedB = [...b].sort();
469
+ return sortedA.every((val, idx) => val === sortedB[idx]);
470
+ }
471
+ function deriveUserAwareSeatArrays(statesData, currentUserId) {
472
+ const myReserved = [];
473
+ const otherReserved = [];
474
+ const unavailable = [];
475
+ Object.entries(statesData).forEach(([seatId, entry]) => {
476
+ if (!entry) return;
477
+ if (typeof entry === "object" && entry.state) {
478
+ if (entry.state === "unavailable") {
479
+ unavailable.push(seatId);
480
+ } else if (entry.state === "reserved") {
481
+ if (currentUserId && entry.userId === currentUserId) {
482
+ myReserved.push(seatId);
483
+ } else {
484
+ otherReserved.push(seatId);
485
+ }
486
+ }
487
+ }
488
+ });
489
+ return {
490
+ myReserved: myReserved.sort(),
491
+ otherReserved: otherReserved.sort(),
492
+ unavailable: unavailable.sort()
493
+ };
494
+ }
495
+ var INITIAL_SNAPSHOT = {
496
+ states: null,
497
+ loading: true,
498
+ error: null,
499
+ lastUpdated: null,
500
+ myReservedSeats: [],
501
+ otherReservedSeats: [],
502
+ unavailableSeats: []
503
+ };
504
+ function useFirebaseSeatStates(options) {
505
+ const {
506
+ seatMapId,
507
+ currentUserId,
508
+ enabled = true,
509
+ onStateChange,
510
+ onError
511
+ } = options;
512
+ const snapshotRef = useRef({ ...INITIAL_SNAPSHOT });
513
+ const onStateChangeRef = useRef(onStateChange);
514
+ const onErrorRef = useRef(onError);
515
+ const currentUserIdRef = useRef(currentUserId);
516
+ onStateChangeRef.current = onStateChange;
517
+ onErrorRef.current = onError;
518
+ currentUserIdRef.current = currentUserId;
519
+ const subscribe = useCallback(
520
+ (callback) => {
521
+ if (!enabled || !seatMapId) {
522
+ if (snapshotRef.current.loading !== false || snapshotRef.current.states !== null) {
523
+ snapshotRef.current = { ...INITIAL_SNAPSHOT, loading: false };
524
+ callback();
525
+ }
526
+ return () => {
527
+ };
528
+ }
529
+ if (!isFirebaseInitialized()) {
530
+ const errorMsg = "Firebase not initialized. Call initializeFirebaseForViewer first.";
531
+ if (snapshotRef.current.error?.message !== errorMsg) {
532
+ snapshotRef.current = {
533
+ ...INITIAL_SNAPSHOT,
534
+ loading: false,
535
+ error: new Error(errorMsg)
536
+ };
537
+ callback();
538
+ }
539
+ return () => {
540
+ };
541
+ }
542
+ const db = getFirebaseDatabase();
543
+ const statesRef = ref(db, `seatmaps/${seatMapId}/seat_states`);
544
+ console.log("\u{1F525} Firebase Listener - Subscribing to:", `seatmaps/${seatMapId}/seat_states`);
545
+ const handleValue = (dataSnapshot) => {
546
+ const data = dataSnapshot.val();
547
+ const statesData = data || {};
548
+ const derived = deriveUserAwareSeatArrays(
549
+ statesData,
550
+ currentUserIdRef.current
551
+ );
552
+ console.log("\u{1F4E1} Firebase Event Received:", {
553
+ path: `seatmaps/${seatMapId}/seat_states`,
554
+ dataSnapshot: dataSnapshot.exists(),
555
+ rawData: data,
556
+ statesCount: Object.keys(statesData).length,
557
+ derived: {
558
+ myReserved: derived.myReserved,
559
+ otherReserved: derived.otherReserved,
560
+ unavailable: derived.unavailable
561
+ }
562
+ });
563
+ const current = snapshotRef.current;
564
+ const hasChanged = current.loading || !arraysEqualUnordered(current.myReservedSeats, derived.myReserved) || !arraysEqualUnordered(
565
+ current.otherReservedSeats,
566
+ derived.otherReserved
567
+ ) || !arraysEqualUnordered(current.unavailableSeats, derived.unavailable);
568
+ console.log("\u{1F504} Firebase Data Changed?", {
569
+ hasChanged,
570
+ currentOtherReserved: current.otherReservedSeats,
571
+ newOtherReserved: derived.otherReserved
572
+ });
573
+ if (hasChanged) {
574
+ console.log("\u2705 Updating React state with new Firebase data");
575
+ snapshotRef.current = {
576
+ states: statesData,
577
+ loading: false,
578
+ error: null,
579
+ lastUpdated: Date.now(),
580
+ myReservedSeats: derived.myReserved,
581
+ otherReservedSeats: derived.otherReserved,
582
+ unavailableSeats: derived.unavailable
583
+ };
584
+ onStateChangeRef.current?.(statesData);
585
+ callback();
586
+ } else {
587
+ console.log("\u23ED\uFE0F No change detected, skipping update");
588
+ }
589
+ };
590
+ const handleError = (err) => {
591
+ snapshotRef.current = {
592
+ ...snapshotRef.current,
593
+ loading: false,
594
+ error: err
595
+ };
596
+ onErrorRef.current?.(err);
597
+ callback();
598
+ };
599
+ onValue(statesRef, handleValue, handleError);
600
+ return () => {
601
+ console.log("\u{1F525} Firebase Listener - Unsubscribing from:", `seatmaps/${seatMapId}/seat_states`);
602
+ off(statesRef);
603
+ };
604
+ },
605
+ [seatMapId, enabled]
606
+ // Don't include currentUserId - we use currentUserIdRef.current which is always up-to-date
607
+ );
608
+ const getSnapshot = useCallback(() => snapshotRef.current, []);
609
+ const getServerSnapshot = useCallback(() => INITIAL_SNAPSHOT, []);
610
+ const snapshot = useSyncExternalStore(
611
+ subscribe,
612
+ getSnapshot,
613
+ getServerSnapshot
614
+ );
615
+ return {
616
+ states: snapshot.states,
617
+ loading: snapshot.loading,
618
+ error: snapshot.error,
619
+ lastUpdated: snapshot.lastUpdated,
620
+ myReservedSeats: snapshot.myReservedSeats,
621
+ otherReservedSeats: snapshot.otherReservedSeats,
622
+ unavailableSeats: snapshot.unavailableSeats,
623
+ // Legacy alias
624
+ reservedSeats: snapshot.otherReservedSeats
625
+ };
626
+ }
627
+
628
+ // src/firebase/hooks/useFirebaseConfig.ts
629
+ import { useState, useEffect, useCallback as useCallback2, useRef as useRef2 } from "react";
630
+ import { ref as ref2, get, onValue as onValue2, off as off2 } from "firebase/database";
631
+ function useFirebaseConfig(options) {
632
+ const {
633
+ seatMapId,
634
+ enabled = true,
635
+ subscribeToChanges = false,
636
+ onConfigLoad,
637
+ onError
638
+ } = options;
639
+ const [config, setConfig] = useState(null);
640
+ const [loading, setLoading] = useState(true);
641
+ const [error, setError] = useState(null);
642
+ const onConfigLoadRef = useRef2(onConfigLoad);
643
+ const onErrorRef = useRef2(onError);
644
+ onConfigLoadRef.current = onConfigLoad;
645
+ onErrorRef.current = onError;
646
+ const fetchConfig = useCallback2(async () => {
647
+ if (!seatMapId) return;
648
+ if (!isFirebaseInitialized()) {
649
+ setError(
650
+ new Error(
651
+ "Firebase not initialized. Call initializeFirebaseForViewer first."
652
+ )
653
+ );
654
+ setLoading(false);
655
+ return;
656
+ }
657
+ const db = getFirebaseDatabase();
658
+ const seatmapRef = ref2(db, `seatmaps/${seatMapId}`);
659
+ try {
660
+ setLoading(true);
661
+ setError(null);
662
+ const snapshot = await get(seatmapRef);
663
+ const data = snapshot.val();
664
+ if (data) {
665
+ const converted = fromFirebaseSeatMap(data);
666
+ setConfig(converted);
667
+ onConfigLoadRef.current?.(converted);
668
+ } else {
669
+ setError(new Error(`Seat map ${seatMapId} not found in Firebase`));
670
+ }
671
+ } catch (err) {
672
+ const error2 = err instanceof Error ? err : new Error("Unknown error");
673
+ setError(error2);
674
+ onErrorRef.current?.(error2);
675
+ } finally {
676
+ setLoading(false);
677
+ }
678
+ }, [seatMapId]);
679
+ useEffect(() => {
680
+ if (!enabled || !seatMapId) {
681
+ setLoading(false);
682
+ return;
683
+ }
684
+ fetchConfig();
685
+ if (subscribeToChanges && isFirebaseInitialized()) {
686
+ const db = getFirebaseDatabase();
687
+ const metaRef = ref2(db, `seatmaps/${seatMapId}/meta/updated_at`);
688
+ let isFirstSnapshot = true;
689
+ let lastSeenTimestamp = null;
690
+ const handleUpdate = (snapshot) => {
691
+ if (isFirstSnapshot) {
692
+ isFirstSnapshot = false;
693
+ lastSeenTimestamp = snapshot.val();
694
+ return;
695
+ }
696
+ const currentTimestamp = snapshot.val();
697
+ if (snapshot.exists() && currentTimestamp !== lastSeenTimestamp) {
698
+ lastSeenTimestamp = currentTimestamp;
699
+ fetchConfig();
700
+ }
701
+ };
702
+ onValue2(metaRef, handleUpdate);
703
+ return () => {
704
+ off2(metaRef);
705
+ };
706
+ }
707
+ }, [seatMapId, enabled, subscribeToChanges]);
708
+ return {
709
+ config,
710
+ loading,
711
+ error,
712
+ refetch: fetchConfig
713
+ };
714
+ }
715
+
716
+ // src/firebase/hooks/useRealtimeSeatMap.ts
717
+ function useRealtimeSeatMap(options) {
718
+ const {
719
+ seatMapId,
720
+ userId,
721
+ enabled = true,
722
+ subscribeToDesignChanges = false,
723
+ onConfigLoad,
724
+ onStateChange,
725
+ onError
726
+ } = options;
727
+ const {
728
+ config,
729
+ loading: configLoading,
730
+ error: configError,
731
+ refetch
732
+ } = useFirebaseConfig({
733
+ seatMapId,
734
+ enabled,
735
+ subscribeToChanges: subscribeToDesignChanges,
736
+ onConfigLoad,
737
+ onError
738
+ });
739
+ const {
740
+ states: seatStates,
741
+ loading: statesLoading,
742
+ error: statesError,
743
+ lastUpdated,
744
+ myReservedSeats,
745
+ otherReservedSeats,
746
+ unavailableSeats,
747
+ reservedSeats
748
+ } = useFirebaseSeatStates({
749
+ seatMapId,
750
+ currentUserId: userId,
751
+ enabled,
752
+ onStateChange,
753
+ onError
754
+ });
755
+ const loading = configLoading || statesLoading;
756
+ const error = configError || statesError;
757
+ return {
758
+ config,
759
+ loading,
760
+ error,
761
+ myReservedSeats,
762
+ otherReservedSeats,
763
+ unavailableSeats,
764
+ reservedSeats,
765
+ seatStates,
766
+ lastUpdated,
767
+ refetch
768
+ };
769
+ }
441
770
  export {
442
771
  DEFAULT_COLORS,
443
772
  FirebasePaths,
@@ -445,6 +774,7 @@ export {
445
774
  calculateAvailableSeats,
446
775
  calculateCapacity,
447
776
  calculateSeatPrice,
777
+ clearFirebaseInstance,
448
778
  cloneConfig,
449
779
  createDefaultConfig,
450
780
  createIndexUpdates,
@@ -458,10 +788,16 @@ export {
458
788
  fromFirebaseSeatMap,
459
789
  fromFirebaseState,
460
790
  generateId,
791
+ getFirebaseDatabase,
461
792
  getSelectedSeats,
462
793
  importConfigFromJSON,
794
+ initializeFirebaseForViewer,
795
+ isFirebaseInitialized,
463
796
  toFirebaseSeatMap,
464
797
  toFirebaseState,
465
798
  updateConfigTimestamp,
799
+ useFirebaseConfig,
800
+ useFirebaseSeatStates,
801
+ useRealtimeSeatMap,
466
802
  validateSeatMapConfig
467
803
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zonetrix/shared",
3
- "version": "2.4.2",
3
+ "version": "2.4.3",
4
4
  "description": "Shared types and utilities for seat-map-studio packages",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -27,6 +27,10 @@
27
27
  ],
28
28
  "author": "",
29
29
  "license": "MIT",
30
+ "peerDependencies": {
31
+ "firebase": "^11.0.0 || ^12.0.0",
32
+ "react": "^18.0.0 || ^19.0.0"
33
+ },
30
34
  "devDependencies": {
31
35
  "tsup": "^8.0.1",
32
36
  "typescript": "^5.8.3"