@zonetrix/shared 2.4.1 → 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.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);
@@ -109,6 +116,7 @@ function validateSeatMapConfig(config) {
109
116
  if (!config.colors) {
110
117
  warnings.push("Missing color configuration");
111
118
  } else {
119
+ const colors = config.colors;
112
120
  const requiredColors = [
113
121
  "canvasBackground",
114
122
  "stageColor",
@@ -119,7 +127,7 @@ function validateSeatMapConfig(config) {
119
127
  "gridLines"
120
128
  ];
121
129
  requiredColors.forEach((color) => {
122
- if (!config.colors[color] || typeof config.colors[color] !== "string") {
130
+ if (!colors[color] || typeof colors[color] !== "string") {
123
131
  warnings.push(`Missing or invalid color: ${color}`);
124
132
  }
125
133
  });
@@ -179,10 +187,10 @@ function validateSeatMapConfig(config) {
179
187
  };
180
188
  }
181
189
  function isValidSeatState(state) {
182
- return ["available", "reserved", "selected", "unavailable"].includes(state);
190
+ return typeof state === "string" && ["available", "reserved", "selected", "unavailable"].includes(state);
183
191
  }
184
192
  function isValidSeatShape(shape) {
185
- return ["circle", "square", "rounded-square"].includes(shape);
193
+ return typeof shape === "string" && ["circle", "square", "rounded-square"].includes(shape);
186
194
  }
187
195
  function generateId(prefix = "obj") {
188
196
  return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
@@ -487,6 +495,335 @@ function createIndexUpdates(seatMapId, eventId, subEventId) {
487
495
  [`indexes/by_sub_event/${subEventId}/${seatMapId}`]: true
488
496
  };
489
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
+ }
490
827
  // Annotate the CommonJS export names for ESM import in node:
491
828
  0 && (module.exports = {
492
829
  DEFAULT_COLORS,
@@ -495,6 +832,7 @@ function createIndexUpdates(seatMapId, eventId, subEventId) {
495
832
  calculateAvailableSeats,
496
833
  calculateCapacity,
497
834
  calculateSeatPrice,
835
+ clearFirebaseInstance,
498
836
  cloneConfig,
499
837
  createDefaultConfig,
500
838
  createIndexUpdates,
@@ -508,10 +846,16 @@ function createIndexUpdates(seatMapId, eventId, subEventId) {
508
846
  fromFirebaseSeatMap,
509
847
  fromFirebaseState,
510
848
  generateId,
849
+ getFirebaseDatabase,
511
850
  getSelectedSeats,
512
851
  importConfigFromJSON,
852
+ initializeFirebaseForViewer,
853
+ isFirebaseInitialized,
513
854
  toFirebaseSeatMap,
514
855
  toFirebaseState,
515
856
  updateConfigTimestamp,
857
+ useFirebaseConfig,
858
+ useFirebaseSeatStates,
859
+ useRealtimeSeatMap,
516
860
  validateSeatMapConfig
517
861
  });