@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.d.mts +261 -2
- package/dist/index.d.ts +261 -2
- package/dist/index.js +347 -3
- package/dist/index.mjs +340 -3
- package/package.json +5 -1
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 (!
|
|
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
|
});
|