@zonetrix/shared 2.3.0 → 2.4.1

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
@@ -259,15 +259,32 @@ declare function downloadConfigAsFile(config: SeatMapConfig, filename?: string):
259
259
  */
260
260
 
261
261
  /**
262
- * Firebase seat state (excludes 'selected' which is client-only, 'hidden' which is filtered)
262
+ * Firebase seat state type (excludes 'selected' which is client-only, 'hidden' which is filtered)
263
263
  */
264
- type FirebaseSeatState = 'available' | 'reserved' | 'unavailable';
264
+ type FirebaseSeatState = "available" | "reserved" | "unavailable";
265
+ /**
266
+ * Seat state value stored in Firebase (for reserved/unavailable seats)
267
+ * Available seats: key is deleted (doesn't exist)
268
+ * Reserved/Unavailable seats: object with state, userId, and timestamp
269
+ */
270
+ interface FirebaseSeatStateValue {
271
+ state: "reserved" | "unavailable";
272
+ userId?: string;
273
+ timestamp?: number;
274
+ }
275
+ /**
276
+ * Entry in the seat states map - null/undefined means available
277
+ */
278
+ type FirebaseSeatStateEntry = FirebaseSeatStateValue | null;
265
279
  /**
266
280
  * Lightweight seat states map for high-frequency real-time updates
267
281
  * Stored at: seat_states/{seatMapId}
282
+ *
283
+ * Keys: encoded seat IDs
284
+ * Values: null = available, object = reserved/unavailable with user info
268
285
  */
269
286
  interface FirebaseSeatStates {
270
- [seatId: string]: FirebaseSeatState;
287
+ [seatId: string]: FirebaseSeatStateEntry;
271
288
  }
272
289
  /**
273
290
  * Position in Firebase (same structure as app)
@@ -368,6 +385,9 @@ interface FirebaseSeatStatesResult {
368
385
  loading: boolean;
369
386
  error: Error | null;
370
387
  lastUpdated: number | null;
388
+ myReservedSeats: string[];
389
+ otherReservedSeats: string[];
390
+ unavailableSeats: string[];
371
391
  }
372
392
  /**
373
393
  * Result from Firebase config hook
@@ -431,18 +451,28 @@ declare function toFirebaseSeatMap(config: SeatMapConfig, eventId: number, subEv
431
451
  declare function fromFirebaseSeatMap(firebase: FirebaseSeatMap, seatStates?: FirebaseSeatStates): SeatMapConfig;
432
452
  /**
433
453
  * Extract seat states from SeatMapConfig for the lightweight states node
454
+ *
455
+ * Only stores non-available states (reserved/unavailable).
456
+ * Available seats: key is not stored (viewer treats missing keys as available)
457
+ * Hidden seats: not stored (filtered out by viewer)
434
458
  */
435
459
  declare function extractSeatStates(config: SeatMapConfig): FirebaseSeatStates;
436
460
  /**
437
- * Merge Firebase seat states with locally-derived reserved/unavailable arrays
461
+ * Derive user-aware seat arrays from Firebase seat states
462
+ *
463
+ * @param states - Firebase seat states map
464
+ * @param currentUserId - Optional current user ID for user-aware derivation
465
+ * @returns Object with myReservedSeats, otherReservedSeats, and unavailableSeats arrays
438
466
  */
439
- declare function deriveSeatArraysFromStates(states: FirebaseSeatStates): {
440
- reservedSeats: string[];
467
+ declare function deriveSeatArraysFromStates(states: FirebaseSeatStates, currentUserId?: string): {
468
+ myReservedSeats: string[];
469
+ otherReservedSeats: string[];
441
470
  unavailableSeats: string[];
471
+ reservedSeats: string[];
442
472
  };
443
473
  /**
444
474
  * Create index updates for a seat map
445
475
  */
446
476
  declare function createIndexUpdates(seatMapId: string, eventId: number, subEventId: number): Record<string, boolean>;
447
477
 
448
- 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 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 };
478
+ 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 };
package/dist/index.d.ts CHANGED
@@ -259,15 +259,32 @@ declare function downloadConfigAsFile(config: SeatMapConfig, filename?: string):
259
259
  */
260
260
 
261
261
  /**
262
- * Firebase seat state (excludes 'selected' which is client-only, 'hidden' which is filtered)
262
+ * Firebase seat state type (excludes 'selected' which is client-only, 'hidden' which is filtered)
263
263
  */
264
- type FirebaseSeatState = 'available' | 'reserved' | 'unavailable';
264
+ type FirebaseSeatState = "available" | "reserved" | "unavailable";
265
+ /**
266
+ * Seat state value stored in Firebase (for reserved/unavailable seats)
267
+ * Available seats: key is deleted (doesn't exist)
268
+ * Reserved/Unavailable seats: object with state, userId, and timestamp
269
+ */
270
+ interface FirebaseSeatStateValue {
271
+ state: "reserved" | "unavailable";
272
+ userId?: string;
273
+ timestamp?: number;
274
+ }
275
+ /**
276
+ * Entry in the seat states map - null/undefined means available
277
+ */
278
+ type FirebaseSeatStateEntry = FirebaseSeatStateValue | null;
265
279
  /**
266
280
  * Lightweight seat states map for high-frequency real-time updates
267
281
  * Stored at: seat_states/{seatMapId}
282
+ *
283
+ * Keys: encoded seat IDs
284
+ * Values: null = available, object = reserved/unavailable with user info
268
285
  */
269
286
  interface FirebaseSeatStates {
270
- [seatId: string]: FirebaseSeatState;
287
+ [seatId: string]: FirebaseSeatStateEntry;
271
288
  }
272
289
  /**
273
290
  * Position in Firebase (same structure as app)
@@ -368,6 +385,9 @@ interface FirebaseSeatStatesResult {
368
385
  loading: boolean;
369
386
  error: Error | null;
370
387
  lastUpdated: number | null;
388
+ myReservedSeats: string[];
389
+ otherReservedSeats: string[];
390
+ unavailableSeats: string[];
371
391
  }
372
392
  /**
373
393
  * Result from Firebase config hook
@@ -431,18 +451,28 @@ declare function toFirebaseSeatMap(config: SeatMapConfig, eventId: number, subEv
431
451
  declare function fromFirebaseSeatMap(firebase: FirebaseSeatMap, seatStates?: FirebaseSeatStates): SeatMapConfig;
432
452
  /**
433
453
  * Extract seat states from SeatMapConfig for the lightweight states node
454
+ *
455
+ * Only stores non-available states (reserved/unavailable).
456
+ * Available seats: key is not stored (viewer treats missing keys as available)
457
+ * Hidden seats: not stored (filtered out by viewer)
434
458
  */
435
459
  declare function extractSeatStates(config: SeatMapConfig): FirebaseSeatStates;
436
460
  /**
437
- * Merge Firebase seat states with locally-derived reserved/unavailable arrays
461
+ * Derive user-aware seat arrays from Firebase seat states
462
+ *
463
+ * @param states - Firebase seat states map
464
+ * @param currentUserId - Optional current user ID for user-aware derivation
465
+ * @returns Object with myReservedSeats, otherReservedSeats, and unavailableSeats arrays
438
466
  */
439
- declare function deriveSeatArraysFromStates(states: FirebaseSeatStates): {
440
- reservedSeats: string[];
467
+ declare function deriveSeatArraysFromStates(states: FirebaseSeatStates, currentUserId?: string): {
468
+ myReservedSeats: string[];
469
+ otherReservedSeats: string[];
441
470
  unavailableSeats: string[];
471
+ reservedSeats: string[];
442
472
  };
443
473
  /**
444
474
  * Create index updates for a seat map
445
475
  */
446
476
  declare function createIndexUpdates(seatMapId: string, eventId: number, subEventId: number): Record<string, boolean>;
447
477
 
448
- 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 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 };
478
+ 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 };
package/dist/index.js CHANGED
@@ -142,7 +142,9 @@ function validateSeatMapConfig(config) {
142
142
  }
143
143
  });
144
144
  const seatIds = config.seats.map((s) => s.id);
145
- const duplicates = seatIds.filter((id, index) => seatIds.indexOf(id) !== index);
145
+ const duplicates = seatIds.filter(
146
+ (id, index) => seatIds.indexOf(id) !== index
147
+ );
146
148
  if (duplicates.length > 0) {
147
149
  errors.push(`Duplicate seat IDs found: ${duplicates.join(", ")}`);
148
150
  }
@@ -257,7 +259,9 @@ function importConfigFromJSON(jsonString) {
257
259
  const config = JSON.parse(jsonString);
258
260
  return config;
259
261
  } catch (error) {
260
- throw new Error(`Failed to parse JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
262
+ throw new Error(
263
+ `Failed to parse JSON: ${error instanceof Error ? error.message : "Unknown error"}`
264
+ );
261
265
  }
262
266
  }
263
267
  function downloadConfigAsFile(config, filename = "seat-map-config.json") {
@@ -390,12 +394,16 @@ function fromFirebaseSeatMap(firebase, seatStates) {
390
394
  const seats = Object.entries(firebase.seats || {}).map(
391
395
  ([encodedId, seat]) => {
392
396
  const id = decodeSeatId(encodedId);
393
- const state = seatStates?.[encodedId] ?? seatStates?.[id] ?? seat.state;
397
+ let effectiveState = seat.state;
398
+ const stateEntry = seatStates?.[encodedId] ?? seatStates?.[id];
399
+ if (stateEntry) {
400
+ effectiveState = stateEntry.state;
401
+ }
394
402
  return {
395
403
  id,
396
404
  position: seat.position,
397
405
  shape: seat.shape,
398
- state: fromFirebaseState(state),
406
+ state: fromFirebaseState(effectiveState),
399
407
  sectionName: seat.sectionName,
400
408
  rowLabel: seat.rowLabel,
401
409
  columnLabel: seat.columnLabel,
@@ -439,23 +447,39 @@ function extractSeatStates(config) {
439
447
  const states = {};
440
448
  for (const seat of config.seats) {
441
449
  if (seat.state === "hidden") continue;
450
+ if (seat.state === "available" || seat.state === "selected") continue;
442
451
  const encodedId = encodeSeatId(seat.id);
443
- states[encodedId] = toFirebaseState(seat.state);
452
+ states[encodedId] = {
453
+ state: seat.state,
454
+ timestamp: Date.now()
455
+ };
444
456
  }
445
457
  return states;
446
458
  }
447
- function deriveSeatArraysFromStates(states) {
448
- const reservedSeats = [];
459
+ function deriveSeatArraysFromStates(states, currentUserId) {
460
+ const myReservedSeats = [];
461
+ const otherReservedSeats = [];
449
462
  const unavailableSeats = [];
450
- for (const [encodedId, state] of Object.entries(states)) {
463
+ for (const [encodedId, entry] of Object.entries(states)) {
464
+ if (!entry) continue;
451
465
  const id = decodeSeatId(encodedId);
452
- if (state === "reserved") {
453
- reservedSeats.push(id);
454
- } else if (state === "unavailable") {
466
+ if (entry.state === "unavailable") {
455
467
  unavailableSeats.push(id);
468
+ } else if (entry.state === "reserved") {
469
+ if (currentUserId && entry.userId === currentUserId) {
470
+ myReservedSeats.push(id);
471
+ } else {
472
+ otherReservedSeats.push(id);
473
+ }
456
474
  }
457
475
  }
458
- return { reservedSeats, unavailableSeats };
476
+ return {
477
+ myReservedSeats,
478
+ otherReservedSeats,
479
+ unavailableSeats,
480
+ // Legacy: all reserved seats (for non-user-aware usage)
481
+ reservedSeats: [...myReservedSeats, ...otherReservedSeats]
482
+ };
459
483
  }
460
484
  function createIndexUpdates(seatMapId, eventId, subEventId) {
461
485
  return {
package/dist/index.mjs CHANGED
@@ -92,7 +92,9 @@ function validateSeatMapConfig(config) {
92
92
  }
93
93
  });
94
94
  const seatIds = config.seats.map((s) => s.id);
95
- const duplicates = seatIds.filter((id, index) => seatIds.indexOf(id) !== index);
95
+ const duplicates = seatIds.filter(
96
+ (id, index) => seatIds.indexOf(id) !== index
97
+ );
96
98
  if (duplicates.length > 0) {
97
99
  errors.push(`Duplicate seat IDs found: ${duplicates.join(", ")}`);
98
100
  }
@@ -207,7 +209,9 @@ function importConfigFromJSON(jsonString) {
207
209
  const config = JSON.parse(jsonString);
208
210
  return config;
209
211
  } catch (error) {
210
- throw new Error(`Failed to parse JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
212
+ throw new Error(
213
+ `Failed to parse JSON: ${error instanceof Error ? error.message : "Unknown error"}`
214
+ );
211
215
  }
212
216
  }
213
217
  function downloadConfigAsFile(config, filename = "seat-map-config.json") {
@@ -340,12 +344,16 @@ function fromFirebaseSeatMap(firebase, seatStates) {
340
344
  const seats = Object.entries(firebase.seats || {}).map(
341
345
  ([encodedId, seat]) => {
342
346
  const id = decodeSeatId(encodedId);
343
- const state = seatStates?.[encodedId] ?? seatStates?.[id] ?? seat.state;
347
+ let effectiveState = seat.state;
348
+ const stateEntry = seatStates?.[encodedId] ?? seatStates?.[id];
349
+ if (stateEntry) {
350
+ effectiveState = stateEntry.state;
351
+ }
344
352
  return {
345
353
  id,
346
354
  position: seat.position,
347
355
  shape: seat.shape,
348
- state: fromFirebaseState(state),
356
+ state: fromFirebaseState(effectiveState),
349
357
  sectionName: seat.sectionName,
350
358
  rowLabel: seat.rowLabel,
351
359
  columnLabel: seat.columnLabel,
@@ -389,23 +397,39 @@ function extractSeatStates(config) {
389
397
  const states = {};
390
398
  for (const seat of config.seats) {
391
399
  if (seat.state === "hidden") continue;
400
+ if (seat.state === "available" || seat.state === "selected") continue;
392
401
  const encodedId = encodeSeatId(seat.id);
393
- states[encodedId] = toFirebaseState(seat.state);
402
+ states[encodedId] = {
403
+ state: seat.state,
404
+ timestamp: Date.now()
405
+ };
394
406
  }
395
407
  return states;
396
408
  }
397
- function deriveSeatArraysFromStates(states) {
398
- const reservedSeats = [];
409
+ function deriveSeatArraysFromStates(states, currentUserId) {
410
+ const myReservedSeats = [];
411
+ const otherReservedSeats = [];
399
412
  const unavailableSeats = [];
400
- for (const [encodedId, state] of Object.entries(states)) {
413
+ for (const [encodedId, entry] of Object.entries(states)) {
414
+ if (!entry) continue;
401
415
  const id = decodeSeatId(encodedId);
402
- if (state === "reserved") {
403
- reservedSeats.push(id);
404
- } else if (state === "unavailable") {
416
+ if (entry.state === "unavailable") {
405
417
  unavailableSeats.push(id);
418
+ } else if (entry.state === "reserved") {
419
+ if (currentUserId && entry.userId === currentUserId) {
420
+ myReservedSeats.push(id);
421
+ } else {
422
+ otherReservedSeats.push(id);
423
+ }
406
424
  }
407
425
  }
408
- return { reservedSeats, unavailableSeats };
426
+ return {
427
+ myReservedSeats,
428
+ otherReservedSeats,
429
+ unavailableSeats,
430
+ // Legacy: all reserved seats (for non-user-aware usage)
431
+ reservedSeats: [...myReservedSeats, ...otherReservedSeats]
432
+ };
409
433
  }
410
434
  function createIndexUpdates(seatMapId, eventId, subEventId) {
411
435
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zonetrix/shared",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "Shared types and utilities for seat-map-studio packages",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -8,9 +8,9 @@
8
8
  "sideEffects": false,
9
9
  "exports": {
10
10
  ".": {
11
+ "types": "./dist/index.d.ts",
11
12
  "import": "./dist/index.mjs",
12
- "require": "./dist/index.js",
13
- "types": "./dist/index.d.ts"
13
+ "require": "./dist/index.js"
14
14
  }
15
15
  },
16
16
  "files": [