@seatlayer/js 0.47.1 → 0.48.0
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/README.md +16 -1
- package/dist/{channelsMode-33ZLORVF.js → channelsMode-NCH5GYHN.js} +3 -3
- package/dist/{chunk-X2R4AQSZ.js → chunk-H4OJF6LE.js} +176 -7
- package/dist/chunk-H4OJF6LE.js.map +1 -0
- package/dist/{chunk-DMEFXZIL.js → chunk-KNMZQZXR.js} +2 -2
- package/dist/{chunk-CF5MS7UR.js → chunk-Q5QT3YLA.js} +186 -59
- package/dist/chunk-Q5QT3YLA.js.map +1 -0
- package/dist/index.cjs +851 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -5
- package/dist/index.d.ts +108 -5
- package/dist/index.js +493 -36
- package/dist/index.js.map +1 -1
- package/dist/{manager-CAIPkUYl.d.cts → manager-DEjbKiqJ.d.cts} +200 -17
- package/dist/{manager-CAIPkUYl.d.ts → manager-DEjbKiqJ.d.ts} +200 -17
- package/dist/manager.cjs +358 -62
- package/dist/manager.cjs.map +1 -1
- package/dist/manager.d.cts +1 -1
- package/dist/manager.d.ts +1 -1
- package/dist/manager.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-CF5MS7UR.js.map +0 -1
- package/dist/chunk-X2R4AQSZ.js.map +0 -1
- /package/dist/{channelsMode-33ZLORVF.js.map → channelsMode-NCH5GYHN.js.map} +0 -0
- /package/dist/{chunk-DMEFXZIL.js.map → chunk-KNMZQZXR.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -431,6 +431,99 @@ async function parse(res) {
|
|
|
431
431
|
}
|
|
432
432
|
return data;
|
|
433
433
|
}
|
|
434
|
+
function record(value) {
|
|
435
|
+
return value && typeof value === "object" ? value : {};
|
|
436
|
+
}
|
|
437
|
+
function finite(primary, legacy, fallback = 0) {
|
|
438
|
+
if (typeof primary === "number" && Number.isFinite(primary)) return primary;
|
|
439
|
+
if (typeof legacy === "number" && Number.isFinite(legacy)) return legacy;
|
|
440
|
+
return fallback;
|
|
441
|
+
}
|
|
442
|
+
function nullableFinite(primary, legacy) {
|
|
443
|
+
if (primary === null) return null;
|
|
444
|
+
if (typeof primary === "number" && Number.isFinite(primary)) return primary;
|
|
445
|
+
if (primary !== void 0) return null;
|
|
446
|
+
if (typeof legacy === "number" && Number.isFinite(legacy)) return legacy;
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
function normalizeSection(value) {
|
|
450
|
+
const row = record(value);
|
|
451
|
+
const bookedValue = finite(row.bookedValue, row.bookedRevenue);
|
|
452
|
+
return { ...row, bookedValue, bookedRevenue: bookedValue };
|
|
453
|
+
}
|
|
454
|
+
function normalizeReportResult(value) {
|
|
455
|
+
const source = record(value);
|
|
456
|
+
const report = record(source.report);
|
|
457
|
+
const byCategory = Array.isArray(report.byCategory) ? report.byCategory.map((value2) => {
|
|
458
|
+
const row = record(value2);
|
|
459
|
+
const bookedValue = finite(row.bookedValue, row.bookedRevenue);
|
|
460
|
+
return { ...row, bookedValue, bookedRevenue: bookedValue };
|
|
461
|
+
}) : [];
|
|
462
|
+
const bySection = Array.isArray(report.bySection) ? report.bySection.map(normalizeSection) : void 0;
|
|
463
|
+
return {
|
|
464
|
+
...source,
|
|
465
|
+
report: { ...report, byCategory, ...bySection ? { bySection } : {} }
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function normalizeControlRoomSnapshot(value) {
|
|
469
|
+
const source = record(value);
|
|
470
|
+
const canonical = record(source.bookedValue);
|
|
471
|
+
const legacy = record(source.revenue);
|
|
472
|
+
const selected = Object.keys(canonical).length ? canonical : legacy;
|
|
473
|
+
const bySectionSource = Array.isArray(canonical.bySection) ? canonical.bySection : Array.isArray(legacy.bySection) ? legacy.bySection : [];
|
|
474
|
+
const bookedValue = {
|
|
475
|
+
...selected,
|
|
476
|
+
gross: finite(canonical.gross, legacy.gross),
|
|
477
|
+
bySection: bySectionSource.map(normalizeSection)
|
|
478
|
+
};
|
|
479
|
+
const velocity = record(source.velocity);
|
|
480
|
+
const velocityRows = Array.isArray(velocity.bySection) ? velocity.bySection.map((value2) => {
|
|
481
|
+
const row = record(value2);
|
|
482
|
+
const rowValue = finite(row.bookedValue, row.grossRevenue);
|
|
483
|
+
return { ...row, bookedValue: rowValue, grossRevenue: rowValue };
|
|
484
|
+
}) : [];
|
|
485
|
+
return {
|
|
486
|
+
...source,
|
|
487
|
+
bookedValue,
|
|
488
|
+
revenue: bookedValue,
|
|
489
|
+
velocity: { ...velocity, bySection: velocityRows }
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function normalizeChannelReportResult(value) {
|
|
493
|
+
const source = record(value);
|
|
494
|
+
const report = record(source.report);
|
|
495
|
+
const includesBookedValue = typeof report.includesBookedValue === "boolean" ? report.includesBookedValue : report.includesRevenue === true;
|
|
496
|
+
const rows = Array.isArray(report.rows) ? report.rows.map((value2) => {
|
|
497
|
+
const row = record(value2);
|
|
498
|
+
const attribution = record(row.attribution);
|
|
499
|
+
const bookedValue = nullableFinite(attribution.bookedValue, attribution.revenue);
|
|
500
|
+
return {
|
|
501
|
+
...row,
|
|
502
|
+
attribution: { ...attribution, bookedValue, revenue: bookedValue }
|
|
503
|
+
};
|
|
504
|
+
}) : [];
|
|
505
|
+
const totals = record(report.totals);
|
|
506
|
+
const totalBookedValue = nullableFinite(totals.bookedValue, totals.revenue);
|
|
507
|
+
return {
|
|
508
|
+
...source,
|
|
509
|
+
report: {
|
|
510
|
+
...report,
|
|
511
|
+
includesBookedValue,
|
|
512
|
+
includesRevenue: includesBookedValue,
|
|
513
|
+
rows,
|
|
514
|
+
totals: { ...totals, bookedValue: totalBookedValue, revenue: totalBookedValue }
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function normalizeChannelReportLink(value) {
|
|
519
|
+
const link = record(value);
|
|
520
|
+
const includesBookedValue = typeof link.includesBookedValue === "boolean" ? link.includesBookedValue : link.includesRevenue === true;
|
|
521
|
+
return {
|
|
522
|
+
...link,
|
|
523
|
+
includesBookedValue,
|
|
524
|
+
includesRevenue: includesBookedValue
|
|
525
|
+
};
|
|
526
|
+
}
|
|
434
527
|
var ManageApiError, ManageApi;
|
|
435
528
|
var init_manageApi = __esm({
|
|
436
529
|
"src/manageApi.ts"() {
|
|
@@ -465,13 +558,28 @@ var init_manageApi = __esm({
|
|
|
465
558
|
}
|
|
466
559
|
return fetch(`${this.base}${path}`, { method, headers, body, credentials: "omit" }).then((r) => parse(r));
|
|
467
560
|
}
|
|
468
|
-
|
|
469
|
-
|
|
561
|
+
async authBlob(path) {
|
|
562
|
+
const res = await fetch(`${this.base}${path}`, {
|
|
563
|
+
method: "GET",
|
|
564
|
+
headers: { Authorization: `Bearer ${this.token}` },
|
|
565
|
+
credentials: "omit"
|
|
566
|
+
});
|
|
567
|
+
if (!res.ok) await parse(res);
|
|
568
|
+
return res.blob();
|
|
470
569
|
}
|
|
471
570
|
// ---- realtime read ----
|
|
472
|
-
/**
|
|
571
|
+
/** Event-pinned organizer geometry. A manage token is never sent to `/pub`. */
|
|
473
572
|
chart(key) {
|
|
474
|
-
return this.
|
|
573
|
+
return this.auth(`/v1/events/${encodeURIComponent(key)}/chart`);
|
|
574
|
+
}
|
|
575
|
+
/** Authenticated bytes for an Event-scoped organizer chart asset. */
|
|
576
|
+
asset(key, asset) {
|
|
577
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(asset)) {
|
|
578
|
+
return Promise.reject(new ManageApiError(404, "not_found", "not_found"));
|
|
579
|
+
}
|
|
580
|
+
return this.authBlob(
|
|
581
|
+
`/v1/events/${encodeURIComponent(key)}/assets/${encodeURIComponent(asset)}`
|
|
582
|
+
);
|
|
475
583
|
}
|
|
476
584
|
/**
|
|
477
585
|
* The ORGANIZER's seat map: physical state, token-authed.
|
|
@@ -531,6 +639,31 @@ var init_manageApi = __esm({
|
|
|
531
639
|
setHoldTtl(key, holdTtlMs) {
|
|
532
640
|
return this.auth(`/v1/events/${encodeURIComponent(key)}/hold-ttl`, { method: "POST", body: { holdTtlMs } });
|
|
533
641
|
}
|
|
642
|
+
// ---- Platform inventory booking history (token) ----
|
|
643
|
+
/** Inventory lifecycle by stable integrator bookingRef. Absent on Managed. */
|
|
644
|
+
bookings(key, query = {}) {
|
|
645
|
+
const params = new URLSearchParams();
|
|
646
|
+
if (query.q) params.set("q", query.q);
|
|
647
|
+
if (query.state) params.set("state", query.state);
|
|
648
|
+
if (query.cursor) params.set("cursor", query.cursor);
|
|
649
|
+
if (query.limit != null) params.set("limit", String(query.limit));
|
|
650
|
+
const qs = params.toString();
|
|
651
|
+
return this.auth(`/v1/events/${encodeURIComponent(key)}/bookings${qs ? `?${qs}` : ""}`);
|
|
652
|
+
}
|
|
653
|
+
/** Exact configured-value snapshot plus book/replay/cancellation audit. */
|
|
654
|
+
booking(key, bookingRef) {
|
|
655
|
+
return this.auth(
|
|
656
|
+
`/v1/events/${encodeURIComponent(key)}/bookings/${encodeURIComponent(bookingRef)}`
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
/** Alias matching the server SDK vocabulary. */
|
|
660
|
+
listBookings(key, query = {}) {
|
|
661
|
+
return this.bookings(key, query);
|
|
662
|
+
}
|
|
663
|
+
/** Alias matching the server SDK vocabulary. */
|
|
664
|
+
retrieveBooking(key, bookingRef) {
|
|
665
|
+
return this.booking(key, bookingRef);
|
|
666
|
+
}
|
|
534
667
|
// ---- availability windows (token) ----
|
|
535
668
|
/** The organizer's current per section/zone availability windows (needs
|
|
536
669
|
* `event:view`). Ids absent from `rules` are open / on sale. */
|
|
@@ -712,10 +845,46 @@ var init_manageApi = __esm({
|
|
|
712
845
|
}
|
|
713
846
|
// ---- reports (token) ----
|
|
714
847
|
report(key) {
|
|
715
|
-
return this.auth(`/v1/events/${encodeURIComponent(key)}/report`);
|
|
848
|
+
return this.auth(`/v1/events/${encodeURIComponent(key)}/report`).then(normalizeReportResult);
|
|
716
849
|
}
|
|
717
850
|
controlRoom(key, windowMinutes = 15) {
|
|
718
|
-
return this.auth(
|
|
851
|
+
return this.auth(
|
|
852
|
+
`/v1/events/${encodeURIComponent(key)}/control-room?window=${windowMinutes}`
|
|
853
|
+
).then(normalizeControlRoomSnapshot);
|
|
854
|
+
}
|
|
855
|
+
/** Allocation beside immutable booking-time channel attribution. */
|
|
856
|
+
channelReport(key) {
|
|
857
|
+
return this.auth(
|
|
858
|
+
`/v1/events/${encodeURIComponent(key)}/channels/report`
|
|
859
|
+
).then(normalizeChannelReportResult);
|
|
860
|
+
}
|
|
861
|
+
createChannelReportLink(key, channelId, input = {}) {
|
|
862
|
+
return this.auth(
|
|
863
|
+
`/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/report-links`,
|
|
864
|
+
{ method: "POST", body: input }
|
|
865
|
+
).then((value) => {
|
|
866
|
+
const reveal = record(value);
|
|
867
|
+
return { ...reveal, link: normalizeChannelReportLink(reveal.link) };
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
channelReportLinks(key, channelId) {
|
|
871
|
+
return this.auth(
|
|
872
|
+
`/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/report-links`
|
|
873
|
+
).then((value) => {
|
|
874
|
+
const result = record(value);
|
|
875
|
+
return {
|
|
876
|
+
links: Array.isArray(result.links) ? result.links.map(normalizeChannelReportLink) : []
|
|
877
|
+
};
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
revokeChannelReportLink(key, channelId, linkId) {
|
|
881
|
+
return this.auth(
|
|
882
|
+
`/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/report-links/${encodeURIComponent(linkId)}`,
|
|
883
|
+
{ method: "DELETE" }
|
|
884
|
+
).then((value) => {
|
|
885
|
+
const result = record(value);
|
|
886
|
+
return { ...result, link: normalizeChannelReportLink(result.link) };
|
|
887
|
+
});
|
|
719
888
|
}
|
|
720
889
|
log(key, opts = {}) {
|
|
721
890
|
const params = new URLSearchParams();
|
|
@@ -3957,11 +4126,13 @@ __export(index_exports, {
|
|
|
3957
4126
|
markerOf: () => markerOf,
|
|
3958
4127
|
mutationCount: () => mutationCount,
|
|
3959
4128
|
needsMoveConfirmation: () => needsMoveConfirmation,
|
|
4129
|
+
parseTicketOfferAvailability: () => parseTicketOfferAvailability,
|
|
3960
4130
|
planAssignment: () => planAssignment,
|
|
3961
4131
|
retryAfterCopy: () => retryAfterCopy,
|
|
3962
4132
|
selectionSources: () => selectionSources,
|
|
3963
4133
|
stateBadge: () => stateBadge,
|
|
3964
|
-
suggestMarker: () => suggestMarker
|
|
4134
|
+
suggestMarker: () => suggestMarker,
|
|
4135
|
+
ticketOfferPrices: () => ticketOfferPrices
|
|
3965
4136
|
});
|
|
3966
4137
|
module.exports = __toCommonJS(index_exports);
|
|
3967
4138
|
|
|
@@ -4444,9 +4615,54 @@ var PubApi = class {
|
|
|
4444
4615
|
}
|
|
4445
4616
|
return data;
|
|
4446
4617
|
}
|
|
4618
|
+
/**
|
|
4619
|
+
* Binary counterpart to `request`. Buyer media needs the same in-memory
|
|
4620
|
+
* bearer/refresh rules as JSON, but returns bytes that the picker turns into
|
|
4621
|
+
* a blob URL. The bearer stays in the Authorization header and is never
|
|
4622
|
+
* appended to `path`.
|
|
4623
|
+
*/
|
|
4624
|
+
async requestBlob(path, retried = {}) {
|
|
4625
|
+
const headers = {};
|
|
4626
|
+
const authorization = await this.access?.authorization(retried.auth ? "unauthorized" : "initial");
|
|
4627
|
+
if (authorization) headers.Authorization = authorization;
|
|
4628
|
+
const res = await fetch(`${this.base}${path}`, { method: "GET", headers, credentials: "omit" });
|
|
4629
|
+
if (res.ok) return res.blob();
|
|
4630
|
+
const isJson = (res.headers.get("content-type") ?? "").includes("application/json");
|
|
4631
|
+
const data = isJson ? await res.json().catch(() => null) : null;
|
|
4632
|
+
const code = data?.code ?? data?.error;
|
|
4633
|
+
if (this.access?.configured && (res.status === 401 || res.status === 403 || res.status === 422)) {
|
|
4634
|
+
const refreshed = await this.access.handleFailure(res.status, code);
|
|
4635
|
+
if (refreshed && !retried.auth) return this.requestBlob(path, { ...retried, auth: true });
|
|
4636
|
+
}
|
|
4637
|
+
let retryAfterS;
|
|
4638
|
+
if (res.status === 429) {
|
|
4639
|
+
retryAfterS = parseRetryAfter(res.headers.get("Retry-After"), data?.retryAfterSeconds) ?? DEFAULT_RATE_LIMIT_WAIT_S;
|
|
4640
|
+
if (!retried.rateLimit && retryAfterS <= MAX_RATE_LIMIT_WAIT_S) {
|
|
4641
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfterS * 1e3));
|
|
4642
|
+
return this.requestBlob(path, { ...retried, rateLimit: true });
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
throw new ApiError(
|
|
4646
|
+
res.status,
|
|
4647
|
+
data?.error ?? `request_failed_${res.status}`,
|
|
4648
|
+
code,
|
|
4649
|
+
void 0,
|
|
4650
|
+
void 0,
|
|
4651
|
+
retryAfterS
|
|
4652
|
+
);
|
|
4653
|
+
}
|
|
4447
4654
|
chart(key) {
|
|
4448
4655
|
return this.request(`/pub/events/${encodeURIComponent(key)}/chart`);
|
|
4449
4656
|
}
|
|
4657
|
+
/** Authenticated bytes for an Event-scoped authored view image. */
|
|
4658
|
+
asset(key, asset) {
|
|
4659
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(asset)) {
|
|
4660
|
+
return Promise.reject(new ApiError(404, "not_found", "not_found"));
|
|
4661
|
+
}
|
|
4662
|
+
return this.requestBlob(
|
|
4663
|
+
`/pub/events/${encodeURIComponent(key)}/assets/${encodeURIComponent(asset)}`
|
|
4664
|
+
);
|
|
4665
|
+
}
|
|
4450
4666
|
objects(key) {
|
|
4451
4667
|
return this.request(`/pub/events/${encodeURIComponent(key)}/objects?compact=1`);
|
|
4452
4668
|
}
|
|
@@ -4497,6 +4713,10 @@ var PubApi = class {
|
|
|
4497
4713
|
paymentOptions(key) {
|
|
4498
4714
|
return this.request(`/pub/events/${encodeURIComponent(key)}/payment-options`);
|
|
4499
4715
|
}
|
|
4716
|
+
/** Server-resolved active ticket offers and category prices. */
|
|
4717
|
+
availability(key, live = false) {
|
|
4718
|
+
return this.request(`/pub/events/${encodeURIComponent(key)}/availability${live ? "?live=1" : ""}`);
|
|
4719
|
+
}
|
|
4500
4720
|
/**
|
|
4501
4721
|
* Turn a live hold into an order and start a payment.
|
|
4502
4722
|
*
|
|
@@ -4545,12 +4765,11 @@ var PubApi = class {
|
|
|
4545
4765
|
/**
|
|
4546
4766
|
* What PickerController opens its own socket with.
|
|
4547
4767
|
*
|
|
4548
|
-
* Empty for an access-scoped client: a
|
|
4768
|
+
* Empty for an access-scoped client: a scoped audience authenticates with a
|
|
4549
4769
|
* subprotocol ticket, which a URL-only constructor cannot carry, so the SDK's
|
|
4550
4770
|
* BuyerRealtimeClient owns that socket instead and the controller skips its
|
|
4551
4771
|
* own (an empty URL is its documented "no live feed" contract). A tokenless
|
|
4552
|
-
* public client returns exactly the URL it always has
|
|
4553
|
-
* public picker's realtime path changes.
|
|
4772
|
+
* Managed public client returns exactly the URL it always has.
|
|
4554
4773
|
*/
|
|
4555
4774
|
socketUrl(key) {
|
|
4556
4775
|
return this.accessScoped ? "" : this.subscribeUrl(key);
|
|
@@ -4993,7 +5212,7 @@ var SeatingChart = class {
|
|
|
4993
5212
|
this.tipEl.style.display = "none";
|
|
4994
5213
|
return;
|
|
4995
5214
|
}
|
|
4996
|
-
const
|
|
5215
|
+
const money2 = (() => {
|
|
4997
5216
|
try {
|
|
4998
5217
|
return new Intl.NumberFormat(void 0, { style: "currency", currency: details.currency }).format(details.price);
|
|
4999
5218
|
} catch {
|
|
@@ -5001,7 +5220,7 @@ var SeatingChart = class {
|
|
|
5001
5220
|
}
|
|
5002
5221
|
})();
|
|
5003
5222
|
const statusLine = details.status === "free" ? "" : `<div style="margin-top:5px;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:#fca5a5;font-weight:700">${details.status === "held" ? (0, import_core.t)("map.statusHeld") : (0, import_core.t)("map.statusTaken")}</div>`;
|
|
5004
|
-
this.tipEl.innerHTML = `<div style="font-weight:700;font-size:13px">${details.label}</div><div style="display:flex;align-items:center;gap:6px;margin-top:4px;color:#c7cddc"><span style="width:9px;height:9px;border-radius:50%;flex:none;background:${details.categoryColor}"></span><span>${details.categoryLabel}</span><span style="margin-left:auto;font-weight:700;color:#fff">${
|
|
5223
|
+
this.tipEl.innerHTML = `<div style="font-weight:700;font-size:13px">${details.label}</div><div style="display:flex;align-items:center;gap:6px;margin-top:4px;color:#c7cddc"><span style="width:9px;height:9px;border-radius:50%;flex:none;background:${details.categoryColor}"></span><span>${details.categoryLabel}</span><span style="margin-left:auto;font-weight:700;color:#fff">${money2}</span></div>` + statusLine;
|
|
5005
5224
|
this.tipEl.style.display = "block";
|
|
5006
5225
|
this.placeTooltip();
|
|
5007
5226
|
}
|
|
@@ -5895,6 +6114,211 @@ var EmbeddedDesigner = class {
|
|
|
5895
6114
|
var import_core2 = require("@seatlayer/core");
|
|
5896
6115
|
var import_panorama = require("@seatlayer/core/view3d/crossfade/panorama");
|
|
5897
6116
|
var import_panoramaDelivery = require("@seatlayer/core/view/panoramaDelivery");
|
|
6117
|
+
|
|
6118
|
+
// src/buyerAssets.ts
|
|
6119
|
+
var SAFE_ASSET = /^[a-zA-Z0-9._-]+$/;
|
|
6120
|
+
function buyerEventAssetReference(value) {
|
|
6121
|
+
let url;
|
|
6122
|
+
try {
|
|
6123
|
+
url = new URL(value, "https://seatlayer.invalid");
|
|
6124
|
+
} catch {
|
|
6125
|
+
return null;
|
|
6126
|
+
}
|
|
6127
|
+
if (url.search || url.hash) return null;
|
|
6128
|
+
const match = /^\/pub\/events\/([^/]+)\/assets\/([^/]+)$/.exec(url.pathname);
|
|
6129
|
+
if (!match) return null;
|
|
6130
|
+
try {
|
|
6131
|
+
const eventKey = decodeURIComponent(match[1]);
|
|
6132
|
+
const asset = decodeURIComponent(match[2]);
|
|
6133
|
+
if (!eventKey || !SAFE_ASSET.test(asset)) return null;
|
|
6134
|
+
return { eventKey, asset };
|
|
6135
|
+
} catch {
|
|
6136
|
+
return null;
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6139
|
+
function looksLikeBuyerAsset(value) {
|
|
6140
|
+
try {
|
|
6141
|
+
return /^\/pub\/events\/[^/]+\/assets(?:\/|$)/.test(
|
|
6142
|
+
new URL(value, "https://seatlayer.invalid").pathname
|
|
6143
|
+
);
|
|
6144
|
+
} catch {
|
|
6145
|
+
return false;
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
var BuyerAssetObjectUrls = class {
|
|
6149
|
+
constructor(eventKey, load) {
|
|
6150
|
+
this.eventKey = eventKey;
|
|
6151
|
+
this.load = load;
|
|
6152
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
6153
|
+
this.created = /* @__PURE__ */ new Set();
|
|
6154
|
+
this.disposed = false;
|
|
6155
|
+
}
|
|
6156
|
+
/**
|
|
6157
|
+
* External organizer/CDN URLs pass through unchanged. SeatLayer event assets
|
|
6158
|
+
* never do: they require the transport, and a reference for another Event is
|
|
6159
|
+
* refused instead of being loaded anonymously.
|
|
6160
|
+
*/
|
|
6161
|
+
resolve(reference) {
|
|
6162
|
+
const parsed = buyerEventAssetReference(reference);
|
|
6163
|
+
if (!parsed) {
|
|
6164
|
+
return Promise.resolve(looksLikeBuyerAsset(reference) ? null : reference);
|
|
6165
|
+
}
|
|
6166
|
+
if (parsed.eventKey !== this.eventKey || !this.load || this.disposed) return Promise.resolve(null);
|
|
6167
|
+
const existing = this.pending.get(reference);
|
|
6168
|
+
if (existing) return existing;
|
|
6169
|
+
const task = this.load(this.eventKey, parsed.asset).then((blob) => {
|
|
6170
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
6171
|
+
if (this.disposed) {
|
|
6172
|
+
URL.revokeObjectURL(objectUrl);
|
|
6173
|
+
return null;
|
|
6174
|
+
}
|
|
6175
|
+
this.created.add(objectUrl);
|
|
6176
|
+
return objectUrl;
|
|
6177
|
+
}).catch((error) => {
|
|
6178
|
+
this.pending.delete(reference);
|
|
6179
|
+
throw error;
|
|
6180
|
+
});
|
|
6181
|
+
this.pending.set(reference, task);
|
|
6182
|
+
return task;
|
|
6183
|
+
}
|
|
6184
|
+
dispose() {
|
|
6185
|
+
if (this.disposed) return;
|
|
6186
|
+
this.disposed = true;
|
|
6187
|
+
for (const url of this.created) URL.revokeObjectURL(url);
|
|
6188
|
+
this.created.clear();
|
|
6189
|
+
this.pending.clear();
|
|
6190
|
+
}
|
|
6191
|
+
};
|
|
6192
|
+
|
|
6193
|
+
// src/offerAvailability.ts
|
|
6194
|
+
var SALE_STATES = [
|
|
6195
|
+
"on-sale",
|
|
6196
|
+
"low",
|
|
6197
|
+
"sold-out",
|
|
6198
|
+
"presale",
|
|
6199
|
+
"closed"
|
|
6200
|
+
];
|
|
6201
|
+
function money(value) {
|
|
6202
|
+
if (value === null || value === void 0) return null;
|
|
6203
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
6204
|
+
}
|
|
6205
|
+
function timestamp(value) {
|
|
6206
|
+
if (value === null || value === void 0) return null;
|
|
6207
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
6208
|
+
}
|
|
6209
|
+
function parseSummary(value) {
|
|
6210
|
+
if (value == null) return null;
|
|
6211
|
+
if (typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
6212
|
+
const source = value;
|
|
6213
|
+
const count = source.count;
|
|
6214
|
+
const index = source.index;
|
|
6215
|
+
if (typeof index !== "number" || !Number.isInteger(index) || index < 1) return void 0;
|
|
6216
|
+
if (typeof count !== "number" || !Number.isInteger(count) || count < index) return void 0;
|
|
6217
|
+
const remaining = source.remaining;
|
|
6218
|
+
if (remaining != null && (typeof remaining !== "number" || !Number.isInteger(remaining) || remaining < 0)) {
|
|
6219
|
+
return void 0;
|
|
6220
|
+
}
|
|
6221
|
+
const result = {
|
|
6222
|
+
index,
|
|
6223
|
+
count,
|
|
6224
|
+
remaining: remaining == null ? null : remaining
|
|
6225
|
+
};
|
|
6226
|
+
if (source.id !== void 0) {
|
|
6227
|
+
if (typeof source.id !== "string" || !source.id.trim()) return void 0;
|
|
6228
|
+
result.id = source.id.trim();
|
|
6229
|
+
}
|
|
6230
|
+
if (source.name !== void 0) {
|
|
6231
|
+
if (typeof source.name !== "string" || !source.name.trim()) return void 0;
|
|
6232
|
+
result.name = source.name.trim();
|
|
6233
|
+
}
|
|
6234
|
+
if (source.categoryKey !== void 0) {
|
|
6235
|
+
if (source.categoryKey !== null && (typeof source.categoryKey !== "string" || !source.categoryKey.trim())) {
|
|
6236
|
+
return void 0;
|
|
6237
|
+
}
|
|
6238
|
+
result.categoryKey = source.categoryKey == null ? null : source.categoryKey.trim();
|
|
6239
|
+
}
|
|
6240
|
+
for (const key of ["startsAt", "endsAt"]) {
|
|
6241
|
+
if (source[key] === void 0) continue;
|
|
6242
|
+
const parsed = timestamp(source[key]);
|
|
6243
|
+
if (parsed === void 0) return void 0;
|
|
6244
|
+
result[key] = parsed;
|
|
6245
|
+
}
|
|
6246
|
+
return result;
|
|
6247
|
+
}
|
|
6248
|
+
function parseTicketOfferAvailability(body) {
|
|
6249
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
6250
|
+
const raw = body;
|
|
6251
|
+
const state = SALE_STATES.find((candidate) => candidate === raw.state);
|
|
6252
|
+
if (!state) return null;
|
|
6253
|
+
const fromPrice = money(raw.fromPrice);
|
|
6254
|
+
const previousPrice = money(raw.previousPrice);
|
|
6255
|
+
if (fromPrice === void 0 || previousPrice === void 0) return null;
|
|
6256
|
+
const currency = raw.currency == null ? null : typeof raw.currency === "string" && raw.currency.trim() ? raw.currency.trim() : void 0;
|
|
6257
|
+
if (currency === void 0) return null;
|
|
6258
|
+
const release = parseSummary(raw.release);
|
|
6259
|
+
if (release === void 0) return null;
|
|
6260
|
+
const upcoming = raw.upcoming === void 0 ? null : parseSummary(raw.upcoming);
|
|
6261
|
+
if (upcoming === void 0) return null;
|
|
6262
|
+
let prices = [];
|
|
6263
|
+
if (raw.prices != null) {
|
|
6264
|
+
if (!Array.isArray(raw.prices)) return null;
|
|
6265
|
+
const parsed = [];
|
|
6266
|
+
for (const entry of raw.prices) {
|
|
6267
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
6268
|
+
const row = entry;
|
|
6269
|
+
const categoryKey = typeof row.categoryKey === "string" ? row.categoryKey.trim() : "";
|
|
6270
|
+
if (!categoryKey) return null;
|
|
6271
|
+
const price = money(row.price);
|
|
6272
|
+
const previous = money(row.previousPrice);
|
|
6273
|
+
if (price === void 0 || price === null || previous === void 0) return null;
|
|
6274
|
+
const item = { categoryKey, price, previousPrice: previous };
|
|
6275
|
+
if (row.offerId !== void 0) {
|
|
6276
|
+
if (typeof row.offerId !== "string" || !row.offerId.trim()) return null;
|
|
6277
|
+
item.offerId = row.offerId.trim();
|
|
6278
|
+
}
|
|
6279
|
+
if (row.offerName !== void 0) {
|
|
6280
|
+
if (typeof row.offerName !== "string" || !row.offerName.trim()) return null;
|
|
6281
|
+
item.offerName = row.offerName.trim();
|
|
6282
|
+
}
|
|
6283
|
+
if (row.remaining !== void 0) {
|
|
6284
|
+
if (row.remaining !== null && (typeof row.remaining !== "number" || !Number.isInteger(row.remaining) || row.remaining < 0)) return null;
|
|
6285
|
+
item.remaining = row.remaining == null ? null : row.remaining;
|
|
6286
|
+
}
|
|
6287
|
+
for (const key of ["startsAt", "endsAt"]) {
|
|
6288
|
+
if (row[key] === void 0) continue;
|
|
6289
|
+
const at = timestamp(row[key]);
|
|
6290
|
+
if (at === void 0) return null;
|
|
6291
|
+
item[key] = at;
|
|
6292
|
+
}
|
|
6293
|
+
parsed.push(item);
|
|
6294
|
+
}
|
|
6295
|
+
prices = parsed;
|
|
6296
|
+
}
|
|
6297
|
+
return { state, fromPrice, previousPrice, currency, release, upcoming, prices };
|
|
6298
|
+
}
|
|
6299
|
+
function nextOfferTransitionAt(availability, now) {
|
|
6300
|
+
if (!availability) return null;
|
|
6301
|
+
let next = null;
|
|
6302
|
+
const consider = (at) => {
|
|
6303
|
+
if (at != null && at > now && (next === null || at < next)) next = at;
|
|
6304
|
+
};
|
|
6305
|
+
for (const summary of [availability.release, availability.upcoming]) {
|
|
6306
|
+
consider(summary?.startsAt);
|
|
6307
|
+
consider(summary?.endsAt);
|
|
6308
|
+
}
|
|
6309
|
+
for (const price of availability.prices) {
|
|
6310
|
+
consider(price.startsAt);
|
|
6311
|
+
consider(price.endsAt);
|
|
6312
|
+
}
|
|
6313
|
+
return next;
|
|
6314
|
+
}
|
|
6315
|
+
function ticketOfferPrices(availability) {
|
|
6316
|
+
const map = {};
|
|
6317
|
+
for (const entry of availability?.prices ?? []) map[entry.categoryKey] = entry.price;
|
|
6318
|
+
return map;
|
|
6319
|
+
}
|
|
6320
|
+
|
|
6321
|
+
// src/SeatPicker.ts
|
|
5898
6322
|
var import_meta = {};
|
|
5899
6323
|
var DEFAULT_API_BASE2 = "https://api.seatlayer.io";
|
|
5900
6324
|
var DEFAULT_MAX_SELECTION2 = 10;
|
|
@@ -6116,6 +6540,15 @@ var CSS2 = (
|
|
|
6116
6540
|
.sl-cbbtn svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
6117
6541
|
|
|
6118
6542
|
/* price panel \u2014 one compact filter control replaces the wrapping price-chip row. */
|
|
6543
|
+
.sl-offer{display:none;margin:12px 14px 2px;padding:12px;border:1px solid color-mix(in srgb,var(--sl-accent) 34%,var(--sl-line));
|
|
6544
|
+
border-radius:12px;background:color-mix(in srgb,var(--sl-accent) 8%,var(--sl-surface));color:var(--sl-text)}
|
|
6545
|
+
.sl-offer.has{display:block}.sl-offer-main{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
|
|
6546
|
+
.sl-offer-copy{min-width:0}.sl-offer-kicker{display:block;font-size:9px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-accent)}
|
|
6547
|
+
.sl-offer-name{display:block;margin-top:2px;font-size:13px;font-weight:800;line-height:1.3}.sl-offer-line{display:block;margin-top:3px;font-size:11px;line-height:1.35;color:var(--sl-muted)}
|
|
6548
|
+
.sl-offer-info{position:relative;flex:none}.sl-offer-info>summary{list-style:none;width:25px;height:25px;border:1px solid var(--sl-line);border-radius:999px;
|
|
6549
|
+
display:grid;place-items:center;cursor:pointer;font-size:12px;font-weight:850;color:var(--sl-text);background:var(--sl-surface)}
|
|
6550
|
+
.sl-offer-info>summary::-webkit-details-marker{display:none}.sl-offer-info[open]>summary{border-color:var(--sl-accent);color:var(--sl-accent)}
|
|
6551
|
+
.sl-offer-detail{margin-top:10px;padding-top:9px;border-top:1px solid var(--sl-line);font-size:10.5px;line-height:1.45;color:var(--sl-muted)}
|
|
6119
6552
|
.sl-sec{padding:14px 14px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
|
|
6120
6553
|
.sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
|
|
6121
6554
|
.sl-price-select{min-height:32px;max-width:130px;padding:5px 28px 5px 9px;border:1px solid var(--sl-line);border-radius:9px;
|
|
@@ -6126,6 +6559,7 @@ var CSS2 = (
|
|
|
6126
6559
|
padding:0 6px;margin:0 -6px;border-radius:8px;cursor:pointer;transition:background .15s}
|
|
6127
6560
|
.sl-price-row:hover,.sl-price-row:focus-visible{background:color-mix(in srgb,var(--sl-line) 40%,transparent)}
|
|
6128
6561
|
.sl-price-row.sl-active{background:color-mix(in srgb,var(--sl-accent) 9%,transparent)}
|
|
6562
|
+
.sl-price-was{margin-left:auto;color:var(--sl-muted);font-size:10px;text-decoration:line-through}.sl-price-offer{display:block;color:var(--sl-accent);font-size:9px;font-weight:750}
|
|
6129
6563
|
.sl-price-row.sl-active .sl-price-label{color:var(--sl-accent)}
|
|
6130
6564
|
.sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
|
|
6131
6565
|
.sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
|
|
@@ -6851,10 +7285,17 @@ var SeatPicker = class _SeatPicker {
|
|
|
6851
7285
|
this.ro = null;
|
|
6852
7286
|
this.holdTimer = null;
|
|
6853
7287
|
this.toastTimer = null;
|
|
7288
|
+
this.offerRefreshTimer = null;
|
|
7289
|
+
/** Armed only when the offer schedule has a known future transition (or as a
|
|
7290
|
+
* bounded retry after a failed read) — never a fixed-cadence poll. */
|
|
7291
|
+
this.offerBoundaryTimer = null;
|
|
7292
|
+
this.offerVisibilityHandler = null;
|
|
6854
7293
|
/** Short-lived UI motion timers; all are cancelled on destroy. */
|
|
6855
7294
|
this.motionTimers = /* @__PURE__ */ new Set();
|
|
6856
7295
|
// state
|
|
6857
7296
|
this.currency = "USD";
|
|
7297
|
+
this.eventTimezone = null;
|
|
7298
|
+
this.offerAvailability = null;
|
|
6858
7299
|
this.hold = null;
|
|
6859
7300
|
/** Latest server expiry for the open hold (moves on extend). */
|
|
6860
7301
|
this.holdExpiresAt = 0;
|
|
@@ -6913,6 +7354,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
6913
7354
|
this.secCardEl = null;
|
|
6914
7355
|
this.viewEl = null;
|
|
6915
7356
|
this.viewCleanup = null;
|
|
7357
|
+
/** Supersedes an older authored-view byte request when another seat is opened. */
|
|
7358
|
+
this.seatViewGen = 0;
|
|
6916
7359
|
this.allSeatsCache = null;
|
|
6917
7360
|
// F3 minimap
|
|
6918
7361
|
this.miniCanvas = null;
|
|
@@ -6968,6 +7411,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
6968
7411
|
if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
|
|
6969
7412
|
if (!options.container) throw new Error("seatmap: `container` is required (or use SeatPicker.open())");
|
|
6970
7413
|
this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
|
|
7414
|
+
this.hostPricing = options.pricing;
|
|
6971
7415
|
this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
|
|
6972
7416
|
this.access = options.transport ? null : createBuyerAccessContext(options, {
|
|
6973
7417
|
onExpired: (event) => {
|
|
@@ -6984,6 +7428,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
6984
7428
|
onObjectUnavailable: (event) => this.opts.onSelectedObjectUnavailable?.(event)
|
|
6985
7429
|
});
|
|
6986
7430
|
this.api = options.transport ?? this.pubApi;
|
|
7431
|
+
this.buyerAssetUrls = new BuyerAssetObjectUrls(
|
|
7432
|
+
options.event,
|
|
7433
|
+
this.api.asset ? (key, asset) => this.api.asset(key, asset) : void 0
|
|
7434
|
+
);
|
|
6987
7435
|
if (options.checkout === "hosted" && !this.pubApi) {
|
|
6988
7436
|
console.warn(
|
|
6989
7437
|
'seatlayer: checkout: "hosted" needs the widget\'s own transport \u2014 a custom `transport` owns its backend, so the picker is staying on onCheckout for this mount.'
|
|
@@ -7009,6 +7457,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7009
7457
|
},
|
|
7010
7458
|
onStatusChange: () => {
|
|
7011
7459
|
this.syncPrices();
|
|
7460
|
+
this.scheduleOfferRefresh(true);
|
|
7012
7461
|
this.evictTakenSelections();
|
|
7013
7462
|
this.detectBooked();
|
|
7014
7463
|
this.refreshMinimap();
|
|
@@ -7111,7 +7560,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7111
7560
|
}
|
|
7112
7561
|
}
|
|
7113
7562
|
const sightHtml = hasStage && distance != null ? `<div class="sl-confirm-sight">${(0, import_core2.t)("picker.sightline", { m: distance })}</div>` : "";
|
|
7114
|
-
const viewBtn = realPhoto ? `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: seat.label })}"><img class="sl-confirm-thumb"
|
|
7563
|
+
const viewBtn = realPhoto ? `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: seat.label })}"><img class="sl-confirm-thumb" alt="" /><span class="sl-confirm-thumb-badge">\u{1F52D} ${this.tf("picker.viewFromHere", "View from here")}</span></button>` : `<button type="button" class="sl-confirm-view sl-confirm-viewbtn" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: seat.label })}"><span aria-hidden="true">\u{1F52D}</span><span>${this.tf("picker.viewFromHere", "View from here")}</span></button>`;
|
|
7115
7564
|
return viewBtn + sightHtml;
|
|
7116
7565
|
}
|
|
7117
7566
|
/** "See it in 3D" (2D) / "View from this seat" (already in 3D) action for the
|
|
@@ -7430,6 +7879,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7430
7879
|
</div>
|
|
7431
7880
|
<div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
|
|
7432
7881
|
<div class="sl-filters" data-ref="filters"></div>
|
|
7882
|
+
<div class="sl-offer" data-ref="offer" role="status" aria-live="polite"></div>
|
|
7433
7883
|
<div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>Ticket prices</span></div>
|
|
7434
7884
|
<div class="sl-prices" data-ref="prices"></div>
|
|
7435
7885
|
<div class="sl-live" data-ref="live" role="status" aria-live="polite"><span class="dot" aria-hidden="true"></span><span data-ref="liveText">Live availability \u2014 seats update in real time</span></div>
|
|
@@ -7450,6 +7900,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
7450
7900
|
this.els[el2.dataset.ref] = el2;
|
|
7451
7901
|
});
|
|
7452
7902
|
this.mapHost = this.els.map;
|
|
7903
|
+
void this.refreshOfferAvailability(false);
|
|
7904
|
+
if (this.api.availability) {
|
|
7905
|
+
this.offerVisibilityHandler = () => {
|
|
7906
|
+
if (!document.hidden && !this.destroyed) void this.refreshOfferAvailability(false);
|
|
7907
|
+
};
|
|
7908
|
+
document.addEventListener("visibilitychange", this.offerVisibilityHandler);
|
|
7909
|
+
}
|
|
7453
7910
|
const applyLayout = () => {
|
|
7454
7911
|
const w = root.clientWidth;
|
|
7455
7912
|
if (w <= 0) return;
|
|
@@ -7554,7 +8011,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7554
8011
|
}
|
|
7555
8012
|
this.els.boot.remove();
|
|
7556
8013
|
this.startRealtime();
|
|
7557
|
-
this.salesClosed = !!info.salesClosed;
|
|
8014
|
+
this.salesClosed = !!info.salesClosed || !!this.opts.readOnly;
|
|
7558
8015
|
this.controller.setViewMode(this.normalizeInitialView(this.opts.initialView));
|
|
7559
8016
|
this.buildRegions();
|
|
7560
8017
|
this.regions["bottom-right"].appendChild(this.els.zoom);
|
|
@@ -7569,6 +8026,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7569
8026
|
const chartTheme = this.controller.doc?.theme;
|
|
7570
8027
|
Object.entries(resolveTokens(chartTheme, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
7571
8028
|
this.currency = info.currency ?? this.opts.currency ?? "USD";
|
|
8029
|
+
this.eventTimezone = info.timezone ?? null;
|
|
7572
8030
|
const logoUrl = this.opts.theme?.logoUrl ?? chartTheme?.logoUrl;
|
|
7573
8031
|
if (logoUrl) this.els.logo.innerHTML = `<img src="${logoUrl}" alt="">`;
|
|
7574
8032
|
else this.els.logo.textContent = (this.opts.theme?.brandName ?? chartTheme?.brandName ?? info.eventName ?? "?").slice(0, 1).toUpperCase();
|
|
@@ -7800,8 +8258,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
7800
8258
|
* idempotent DOM apply used at load and on transition.
|
|
7801
8259
|
*/
|
|
7802
8260
|
setSalesClosed(closed) {
|
|
7803
|
-
|
|
7804
|
-
this.salesClosed
|
|
8261
|
+
const next = closed || !!this.opts.readOnly;
|
|
8262
|
+
if (this.salesClosed === next) return;
|
|
8263
|
+
this.salesClosed = next;
|
|
7805
8264
|
this.applySalesClosed();
|
|
7806
8265
|
}
|
|
7807
8266
|
applySalesClosed() {
|
|
@@ -8685,6 +9144,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
8685
9144
|
el2.innerHTML = `<div class="sl-confirm-grid">` + identityFields + `</div><div class="sl-confirm-cat"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span><span class="sl-confirm-cat-name">${safe(details?.categoryLabel ?? cat?.label ?? seat.categoryKey)}</span>` + (price != null ? `<span class="sl-confirm-price">${this.money(price)}</span>` : "") + `</div><div class="sl-confirm-body">` + this.wheelchairConfirmHtml(details?.wheelchairSpaceType) + this.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() ? this.confirmThumbHtml(seat) : "") + this.see3dConfirmHtml() + `<div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">Cancel</button><button type="button" class="sl-confirm-add"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4 4L19 7"/></svg>Select</button></div></div>`;
|
|
8686
9145
|
this.els.map.appendChild(el2);
|
|
8687
9146
|
this.confirmEl = el2;
|
|
9147
|
+
const thumb = el2.querySelector(".sl-confirm-thumb");
|
|
9148
|
+
if (thumb && seat.viewUrl) {
|
|
9149
|
+
const thumbReference = seat.viewMeta?.previewUrl ?? seat.viewUrl;
|
|
9150
|
+
void this.buyerAssetUrls.resolve(thumbReference).then((url) => {
|
|
9151
|
+
if (url && el2.isConnected && this.confirmEl === el2) thumb.src = url;
|
|
9152
|
+
}).catch((error) => this.opts.onError?.(error));
|
|
9153
|
+
}
|
|
8688
9154
|
this.reanchorConfirm();
|
|
8689
9155
|
el2.querySelector(".sl-confirm-view")?.addEventListener("click", () => void this.openSeatView(seat));
|
|
8690
9156
|
el2.querySelector(".sl-confirm-3d")?.addEventListener("click", () => {
|
|
@@ -8765,6 +9231,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
8765
9231
|
*/
|
|
8766
9232
|
async openSeatView(seat) {
|
|
8767
9233
|
if (!this.root || !this.seatViewEnabled()) return;
|
|
9234
|
+
const generation = ++this.seatViewGen;
|
|
8768
9235
|
const doc = this.controller.doc;
|
|
8769
9236
|
const activeId = this.controller.getActiveFloorId();
|
|
8770
9237
|
const focal = seat.focalPoint ?? doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
@@ -8772,9 +9239,24 @@ var SeatPicker = class _SeatPicker {
|
|
|
8772
9239
|
let caption;
|
|
8773
9240
|
let real = false;
|
|
8774
9241
|
if (seat.viewUrl) {
|
|
9242
|
+
let resolvedUrl;
|
|
9243
|
+
let resolvedPreviewUrl = null;
|
|
9244
|
+
try {
|
|
9245
|
+
const previewReference = seat.viewMeta?.previewUrl;
|
|
9246
|
+
if (previewReference && previewReference !== seat.viewUrl) {
|
|
9247
|
+
resolvedPreviewUrl = await this.buyerAssetUrls.resolve(previewReference);
|
|
9248
|
+
resolvedUrl = seat.viewUrl;
|
|
9249
|
+
} else {
|
|
9250
|
+
resolvedUrl = await this.buyerAssetUrls.resolve(seat.viewUrl);
|
|
9251
|
+
}
|
|
9252
|
+
} catch (error) {
|
|
9253
|
+
if (generation === this.seatViewGen) this.opts.onError?.(error);
|
|
9254
|
+
return;
|
|
9255
|
+
}
|
|
9256
|
+
if (generation !== this.seatViewGen || !resolvedUrl || seat.viewMeta?.previewUrl && seat.viewMeta.previewUrl !== seat.viewUrl && !resolvedPreviewUrl || !this.root || !this.seatViewEnabled()) return;
|
|
8775
9257
|
const view = {
|
|
8776
|
-
url:
|
|
8777
|
-
...
|
|
9258
|
+
url: resolvedUrl,
|
|
9259
|
+
...resolvedPreviewUrl ? { previewUrl: resolvedPreviewUrl } : {},
|
|
8778
9260
|
...seat.viewMeta?.sourceWidth !== void 0 ? { sourceWidth: seat.viewMeta.sourceWidth } : {},
|
|
8779
9261
|
...seat.viewMeta?.sourceHeight !== void 0 ? { sourceHeight: seat.viewMeta.sourceHeight } : {},
|
|
8780
9262
|
...seat.viewMeta?.previewWidth !== void 0 ? { previewWidth: seat.viewMeta.previewWidth } : {},
|
|
@@ -8792,14 +9274,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
8792
9274
|
const { generateSeatPanorama } = await loadPanorama();
|
|
8793
9275
|
pano2 = generateSeatPanorama(seat, focal, this.allSeats());
|
|
8794
9276
|
} catch (err) {
|
|
8795
|
-
this.opts.onError?.(err);
|
|
9277
|
+
if (generation === this.seatViewGen) this.opts.onError?.(err);
|
|
8796
9278
|
return;
|
|
8797
9279
|
}
|
|
8798
|
-
if (!this.root || !this.seatViewEnabled()) return;
|
|
9280
|
+
if (generation !== this.seatViewGen || !this.root || !this.seatViewEnabled()) return;
|
|
8799
9281
|
panoSource = { url: pano2.url, generated: true };
|
|
8800
9282
|
caption = (0, import_core2.t)("picker.illustrationCaption", { m: pano2.distanceM });
|
|
8801
9283
|
}
|
|
8802
|
-
this.closeSeatView();
|
|
9284
|
+
this.closeSeatView(false);
|
|
8803
9285
|
const el2 = document.createElement("div");
|
|
8804
9286
|
el2.className = "sl-view";
|
|
8805
9287
|
el2.setAttribute("role", "dialog");
|
|
@@ -8815,9 +9297,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
8815
9297
|
};
|
|
8816
9298
|
if (delivery.upgradeUrl) {
|
|
8817
9299
|
cancelUpgrade = (0, import_panoramaDelivery.schedulePanoramaUpgrade)(() => {
|
|
8818
|
-
void
|
|
8819
|
-
if (!
|
|
8820
|
-
|
|
9300
|
+
void this.buyerAssetUrls.resolve(delivery.upgradeUrl).then((url) => {
|
|
9301
|
+
if (!url || loadAbort.signal.aborted) return null;
|
|
9302
|
+
return (0, import_panoramaDelivery.loadPanoramaImage)(url, loadAbort.signal).then(() => url);
|
|
9303
|
+
}).then((url) => {
|
|
9304
|
+
if (!url || !el2.isConnected || loadAbort.signal.aborted) return;
|
|
9305
|
+
pano.style.backgroundImage = `url("${url}")`;
|
|
8821
9306
|
}).catch(() => {
|
|
8822
9307
|
});
|
|
8823
9308
|
});
|
|
@@ -8893,7 +9378,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
8893
9378
|
el2.removeEventListener("keydown", onKey);
|
|
8894
9379
|
};
|
|
8895
9380
|
}
|
|
8896
|
-
closeSeatView() {
|
|
9381
|
+
closeSeatView(cancelPending = true) {
|
|
9382
|
+
if (cancelPending) this.seatViewGen += 1;
|
|
8897
9383
|
this.viewCleanup?.();
|
|
8898
9384
|
this.viewCleanup = null;
|
|
8899
9385
|
this.viewEl?.remove();
|
|
@@ -8909,6 +9395,119 @@ var SeatPicker = class _SeatPicker {
|
|
|
8909
9395
|
return `${n} ${this.currency}`;
|
|
8910
9396
|
}
|
|
8911
9397
|
}
|
|
9398
|
+
/**
|
|
9399
|
+
* Sleep until the offer schedule's next known transition, then re-read.
|
|
9400
|
+
*
|
|
9401
|
+
* A far-away boundary is capped: the wake re-reads, learns the (unchanged)
|
|
9402
|
+
* schedule, and re-arms — so a picker left open for days still tracks an
|
|
9403
|
+
* organizer's schedule edits at a cost of one request every few hours. No
|
|
9404
|
+
* future transition means no timer at all; an event with no releases does
|
|
9405
|
+
* zero background traffic. A wake in a hidden tab fetches nothing — the
|
|
9406
|
+
* visibilitychange handler owns catching that tab up.
|
|
9407
|
+
*/
|
|
9408
|
+
scheduleOfferBoundary(availability) {
|
|
9409
|
+
if (this.offerBoundaryTimer) clearTimeout(this.offerBoundaryTimer);
|
|
9410
|
+
this.offerBoundaryTimer = null;
|
|
9411
|
+
if (!this.api.availability || this.destroyed) return;
|
|
9412
|
+
const now = Date.now();
|
|
9413
|
+
const boundary = nextOfferTransitionAt(availability, now);
|
|
9414
|
+
if (boundary == null) return;
|
|
9415
|
+
const MAX_SLEEP_MS = 6 * 36e5;
|
|
9416
|
+
const delay = Math.min(Math.max(boundary - now + 1e3, 1e3), MAX_SLEEP_MS);
|
|
9417
|
+
this.offerBoundaryTimer = setTimeout(() => {
|
|
9418
|
+
this.offerBoundaryTimer = null;
|
|
9419
|
+
if (document.hidden) return;
|
|
9420
|
+
void this.refreshOfferAvailability(false);
|
|
9421
|
+
}, delay);
|
|
9422
|
+
}
|
|
9423
|
+
/** Debounce the no-store offer read behind a burst of seat-status frames. */
|
|
9424
|
+
scheduleOfferRefresh(live) {
|
|
9425
|
+
if (!this.api.availability || this.destroyed) return;
|
|
9426
|
+
if (this.offerRefreshTimer) clearTimeout(this.offerRefreshTimer);
|
|
9427
|
+
this.offerRefreshTimer = setTimeout(() => {
|
|
9428
|
+
this.offerRefreshTimer = null;
|
|
9429
|
+
void this.refreshOfferAvailability(live);
|
|
9430
|
+
}, live ? 180 : 0);
|
|
9431
|
+
}
|
|
9432
|
+
/**
|
|
9433
|
+
* Pull the server's resolved answer. A failed refresh keeps the last truthful
|
|
9434
|
+
* answer: flashing back to a chart price while checkout still charges an
|
|
9435
|
+
* offer is worse than a temporarily stale remaining count.
|
|
9436
|
+
*/
|
|
9437
|
+
async refreshOfferAvailability(live) {
|
|
9438
|
+
if (!this.api.availability || this.destroyed) return;
|
|
9439
|
+
try {
|
|
9440
|
+
const body = await this.api.availability(this.opts.event, live);
|
|
9441
|
+
if (this.destroyed) return;
|
|
9442
|
+
const availability = parseTicketOfferAvailability(body);
|
|
9443
|
+
if (!availability) return;
|
|
9444
|
+
this.offerAvailability = availability;
|
|
9445
|
+
this.scheduleOfferBoundary(availability);
|
|
9446
|
+
const server = ticketOfferPrices(availability);
|
|
9447
|
+
const merged = { ...this.hostPricing?.prices ?? {}, ...server };
|
|
9448
|
+
const pricing = Object.keys(merged).length > 0 || this.hostPricing?.formatter ? { prices: merged, ...this.hostPricing?.formatter ? { formatter: this.hostPricing.formatter } : {} } : void 0;
|
|
9449
|
+
this.setPricing(pricing);
|
|
9450
|
+
this.syncOffer();
|
|
9451
|
+
this.opts.onOfferAvailabilityChange?.(availability);
|
|
9452
|
+
} catch {
|
|
9453
|
+
if (!this.destroyed && !this.offerBoundaryTimer && !document.hidden) {
|
|
9454
|
+
this.offerBoundaryTimer = setTimeout(() => {
|
|
9455
|
+
this.offerBoundaryTimer = null;
|
|
9456
|
+
if (document.hidden) return;
|
|
9457
|
+
void this.refreshOfferAvailability(false);
|
|
9458
|
+
}, 3e4);
|
|
9459
|
+
}
|
|
9460
|
+
}
|
|
9461
|
+
}
|
|
9462
|
+
offerPrice(categoryKey) {
|
|
9463
|
+
if (!categoryKey) return null;
|
|
9464
|
+
return this.offerAvailability?.prices.find((entry) => entry.categoryKey === categoryKey) ?? null;
|
|
9465
|
+
}
|
|
9466
|
+
/** The compact current/upcoming offer card above Ticket prices. */
|
|
9467
|
+
syncOffer() {
|
|
9468
|
+
const host = this.els.offer;
|
|
9469
|
+
if (!host) return;
|
|
9470
|
+
const availability = this.offerAvailability;
|
|
9471
|
+
const active = availability?.release ?? null;
|
|
9472
|
+
const upcoming = !active ? availability?.upcoming ?? null : null;
|
|
9473
|
+
if (!availability || availability.state === "closed" || availability.state === "sold-out" || !active && !upcoming) {
|
|
9474
|
+
host.classList.remove("has");
|
|
9475
|
+
host.replaceChildren();
|
|
9476
|
+
return;
|
|
9477
|
+
}
|
|
9478
|
+
const offer = active ?? upcoming;
|
|
9479
|
+
const main = document.createElement("div");
|
|
9480
|
+
main.className = "sl-offer-main";
|
|
9481
|
+
const copy = document.createElement("div");
|
|
9482
|
+
copy.className = "sl-offer-copy";
|
|
9483
|
+
const kicker = document.createElement("span");
|
|
9484
|
+
kicker.className = "sl-offer-kicker";
|
|
9485
|
+
kicker.textContent = active ? "Current ticket offer" : "Upcoming ticket offer";
|
|
9486
|
+
const name = document.createElement("strong");
|
|
9487
|
+
name.className = "sl-offer-name";
|
|
9488
|
+
name.textContent = offer.name || (active ? "Current offer" : "Scheduled offer");
|
|
9489
|
+
const line = document.createElement("span");
|
|
9490
|
+
line.className = "sl-offer-line";
|
|
9491
|
+
const facts = [];
|
|
9492
|
+
if (active && availability.fromPrice != null) facts.push(this.money(availability.fromPrice / 100));
|
|
9493
|
+
if (active && offer.remaining != null) facts.push(`${offer.remaining} available`);
|
|
9494
|
+
if (active && offer.endsAt != null) facts.push(`until ${formatWhen(offer.endsAt, this.eventTimezone, this.opts.locale)}`);
|
|
9495
|
+
if (upcoming?.startsAt != null) facts.push(`starts ${formatWhen(upcoming.startsAt, this.eventTimezone, this.opts.locale)}`);
|
|
9496
|
+
line.textContent = facts.join(" \xB7 ");
|
|
9497
|
+
copy.append(kicker, name, line);
|
|
9498
|
+
const info = document.createElement("details");
|
|
9499
|
+
info.className = "sl-offer-info";
|
|
9500
|
+
const summary = document.createElement("summary");
|
|
9501
|
+
summary.setAttribute("aria-label", `How the ${name.textContent} offer works`);
|
|
9502
|
+
summary.textContent = "i";
|
|
9503
|
+
const detail = document.createElement("div");
|
|
9504
|
+
detail.className = "sl-offer-detail";
|
|
9505
|
+
detail.textContent = active ? `This price applies automatically to eligible seats. Tickets in active carts temporarily reduce the available quantity; released or expired holds return it. When the offer ends, the next matching offer or normal ticket price takes over.` : `Tickets are available at their normal price now. This scheduled offer will apply automatically to eligible seats when it starts.`;
|
|
9506
|
+
info.append(summary, detail);
|
|
9507
|
+
main.append(copy, info);
|
|
9508
|
+
host.replaceChildren(main);
|
|
9509
|
+
host.classList.add("has");
|
|
9510
|
+
}
|
|
8912
9511
|
/**
|
|
8913
9512
|
* The price the buyer will actually pay for a category (+tier): the host's
|
|
8914
9513
|
* `pricing` override when present, else the chart's stored price. Every
|
|
@@ -8935,9 +9534,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
8935
9534
|
this.els.prices.classList.toggle("sl-expanded", overflow > 1 && this.pricesExpanded);
|
|
8936
9535
|
this.els.prices.innerHTML = shown.map((c) => {
|
|
8937
9536
|
const price = this.catPrice(c);
|
|
9537
|
+
const offer = this.offerPrice(c.key);
|
|
9538
|
+
const previous = offer?.previousPrice != null && offer.previousPrice > offer.price ? offer.previousPrice : null;
|
|
8938
9539
|
const active = this.focusedCatKey === c.key;
|
|
8939
9540
|
const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
|
|
8940
|
-
return `<div class="sl-price-row${dim ? " sl-dim" : ""}${active ? " sl-active" : ""}" data-cat="${c.key}" role="button" tabindex="0" aria-pressed="${active}" title="${active ? "Show all seats" : `Show ${c.label} seats on the map`}"><span class="sl-dot" style="background:${c.color}"></span><span class="sl-price-label">${c.label}</span><span class="sl-price-left">${left[c.key] ?? 0} left</span>` + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
|
|
9541
|
+
return `<div class="sl-price-row${dim ? " sl-dim" : ""}${active ? " sl-active" : ""}" data-cat="${escapeOption(c.key)}" role="button" tabindex="0" aria-pressed="${active}" title="${escapeOption(active ? "Show all seats" : `Show ${c.label} seats on the map`)}"><span class="sl-dot" style="background:${escapeOption(c.color)}"></span><span class="sl-price-label">${escapeOption(c.label)}` + (offer?.offerName ? `<small class="sl-price-offer">${escapeOption(offer.offerName)}</small>` : "") + `</span><span class="sl-price-left">${left[c.key] ?? 0} left</span>` + (previous != null ? `<span class="sl-price-was">${escapeOption(this.money(previous))}</span>` : "") + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
|
|
8941
9542
|
}).join("") + (overflow > 1 ? `<button type="button" class="sl-price-more" aria-expanded="${!collapsed}">` + (collapsed ? `Show all ${doc.categories.length} ticket types` : "Show fewer") + `</button>` : "") + `<div class="sl-status-key" aria-label="Seat status legend"><span class="sl-status-item"><i class="sl-status-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></i>Temporarily held</span><span class="sl-status-item"><i class="sl-status-icon sold" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M7 17L17 7"/></svg></i>Sold</span></div>`;
|
|
8942
9543
|
this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
|
|
8943
9544
|
row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
|
|
@@ -9593,6 +10194,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
9593
10194
|
}
|
|
9594
10195
|
emitHoldChange() {
|
|
9595
10196
|
const hold = this.hold;
|
|
10197
|
+
this.scheduleOfferRefresh(true);
|
|
9596
10198
|
this.opts.onHoldChange?.(
|
|
9597
10199
|
hold,
|
|
9598
10200
|
hold?.seats ?? [],
|
|
@@ -9860,19 +10462,33 @@ var SeatPicker = class _SeatPicker {
|
|
|
9860
10462
|
async seatViewFor3d(seatId) {
|
|
9861
10463
|
const seat = this.allSeats().find((s) => s.id === seatId);
|
|
9862
10464
|
if (!seat) return null;
|
|
9863
|
-
if (seat.viewUrl)
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
10465
|
+
if (seat.viewUrl) {
|
|
10466
|
+
try {
|
|
10467
|
+
const previewReference = seat.viewMeta?.previewUrl;
|
|
10468
|
+
const progressive = !!previewReference && previewReference !== seat.viewUrl;
|
|
10469
|
+
const previewUrl = progressive ? await this.buyerAssetUrls.resolve(previewReference) : null;
|
|
10470
|
+
const url = progressive ? seat.viewUrl : await this.buyerAssetUrls.resolve(seat.viewUrl);
|
|
10471
|
+
if (!url) return null;
|
|
10472
|
+
if (progressive && !previewUrl) return null;
|
|
10473
|
+
return {
|
|
10474
|
+
url,
|
|
10475
|
+
...previewUrl ? { previewUrl } : {},
|
|
10476
|
+
...progressive ? { resolveUrl: (reference) => this.buyerAssetUrls.resolve(reference) } : {},
|
|
10477
|
+
...seat.viewMeta?.sourceWidth !== void 0 ? { sourceWidth: seat.viewMeta.sourceWidth } : {},
|
|
10478
|
+
...seat.viewMeta?.sourceHeight !== void 0 ? { sourceHeight: seat.viewMeta.sourceHeight } : {},
|
|
10479
|
+
...seat.viewMeta?.previewWidth !== void 0 ? { previewWidth: seat.viewMeta.previewWidth } : {},
|
|
10480
|
+
...seat.viewMeta?.previewHeight !== void 0 ? { previewHeight: seat.viewMeta.previewHeight } : {},
|
|
10481
|
+
...seat.viewMeta?.initialBearingDeg !== void 0 ? { initialBearingDeg: seat.viewMeta.initialBearingDeg } : {},
|
|
10482
|
+
...seat.viewMeta?.initialPitchDeg !== void 0 ? { initialPitchDeg: seat.viewMeta.initialPitchDeg } : {},
|
|
10483
|
+
...seat.viewMeta?.coverage ? { coverage: seat.viewMeta.coverage } : {},
|
|
10484
|
+
...seat.viewMeta?.capturedAt ? { capturedAt: seat.viewMeta.capturedAt } : {},
|
|
10485
|
+
...seat.viewMeta?.sourceLabel ? { sourceLabel: seat.viewMeta.sourceLabel } : {}
|
|
10486
|
+
};
|
|
10487
|
+
} catch (error) {
|
|
10488
|
+
this.opts.onError?.(error);
|
|
10489
|
+
return null;
|
|
10490
|
+
}
|
|
10491
|
+
}
|
|
9876
10492
|
const doc = this.controller.doc;
|
|
9877
10493
|
if (!doc) return null;
|
|
9878
10494
|
const activeId = this.controller.getActiveFloorId();
|
|
@@ -10322,6 +10938,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
10322
10938
|
flashOnLiveChange: true,
|
|
10323
10939
|
onStatusChange: () => {
|
|
10324
10940
|
this.syncPrices();
|
|
10941
|
+
this.scheduleOfferRefresh(true);
|
|
10325
10942
|
this.detectBooked();
|
|
10326
10943
|
this.refreshMinimap();
|
|
10327
10944
|
this.pushAvailabilityTo3d();
|
|
@@ -10454,9 +11071,18 @@ var SeatPicker = class _SeatPicker {
|
|
|
10454
11071
|
this.closeSeatView();
|
|
10455
11072
|
this.closeCheckoutPanel();
|
|
10456
11073
|
this.exit3d();
|
|
11074
|
+
this.buyerAssetUrls.dispose();
|
|
10457
11075
|
this.stopHoldTimer();
|
|
10458
11076
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
10459
11077
|
if (this.liveTimer) clearTimeout(this.liveTimer);
|
|
11078
|
+
if (this.offerRefreshTimer) clearTimeout(this.offerRefreshTimer);
|
|
11079
|
+
if (this.offerBoundaryTimer) clearTimeout(this.offerBoundaryTimer);
|
|
11080
|
+
this.offerRefreshTimer = null;
|
|
11081
|
+
this.offerBoundaryTimer = null;
|
|
11082
|
+
if (this.offerVisibilityHandler) {
|
|
11083
|
+
document.removeEventListener("visibilitychange", this.offerVisibilityHandler);
|
|
11084
|
+
this.offerVisibilityHandler = null;
|
|
11085
|
+
}
|
|
10460
11086
|
for (const timer of this.motionTimers) clearTimeout(timer);
|
|
10461
11087
|
this.motionTimers.clear();
|
|
10462
11088
|
this.ro?.disconnect();
|
|
@@ -10566,6 +11192,108 @@ function attachPickerFrame(iframe, opts = {}) {
|
|
|
10566
11192
|
// src/SeatManager.ts
|
|
10567
11193
|
var import_core3 = require("@seatlayer/core");
|
|
10568
11194
|
init_manageApi();
|
|
11195
|
+
|
|
11196
|
+
// src/manageAssets.ts
|
|
11197
|
+
var SAFE_ASSET2 = /^[a-zA-Z0-9._-]+$/;
|
|
11198
|
+
function organizerEventAssetReference(value) {
|
|
11199
|
+
let url;
|
|
11200
|
+
try {
|
|
11201
|
+
url = new URL(value, "https://seatlayer.invalid");
|
|
11202
|
+
} catch {
|
|
11203
|
+
return null;
|
|
11204
|
+
}
|
|
11205
|
+
if (url.search || url.hash) return null;
|
|
11206
|
+
const match = /^\/v1\/events\/([^/]+)\/assets\/([^/]+)$/.exec(url.pathname);
|
|
11207
|
+
if (!match) return null;
|
|
11208
|
+
try {
|
|
11209
|
+
const eventKey = decodeURIComponent(match[1]);
|
|
11210
|
+
const asset = decodeURIComponent(match[2]);
|
|
11211
|
+
if (!eventKey || !SAFE_ASSET2.test(asset)) return null;
|
|
11212
|
+
return { eventKey, asset };
|
|
11213
|
+
} catch {
|
|
11214
|
+
return null;
|
|
11215
|
+
}
|
|
11216
|
+
}
|
|
11217
|
+
function looksLikeOrganizerAsset(value) {
|
|
11218
|
+
try {
|
|
11219
|
+
return /^\/v1\/events\/[^/]+\/assets(?:\/|$)/.test(
|
|
11220
|
+
new URL(value, "https://seatlayer.invalid").pathname
|
|
11221
|
+
);
|
|
11222
|
+
} catch {
|
|
11223
|
+
return false;
|
|
11224
|
+
}
|
|
11225
|
+
}
|
|
11226
|
+
var OrganizerAssetObjectUrls = class {
|
|
11227
|
+
constructor(eventKey, load) {
|
|
11228
|
+
this.eventKey = eventKey;
|
|
11229
|
+
this.load = load;
|
|
11230
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
11231
|
+
this.created = /* @__PURE__ */ new Set();
|
|
11232
|
+
this.disposed = false;
|
|
11233
|
+
}
|
|
11234
|
+
resolve(reference) {
|
|
11235
|
+
const parsed = organizerEventAssetReference(reference);
|
|
11236
|
+
if (!parsed) {
|
|
11237
|
+
return Promise.resolve(looksLikeOrganizerAsset(reference) ? null : reference);
|
|
11238
|
+
}
|
|
11239
|
+
if (parsed.eventKey !== this.eventKey || this.disposed) return Promise.resolve(null);
|
|
11240
|
+
const cacheKey = `${parsed.eventKey}/${parsed.asset}`;
|
|
11241
|
+
const existing = this.pending.get(cacheKey);
|
|
11242
|
+
if (existing) return existing;
|
|
11243
|
+
const task = this.load(parsed.eventKey, parsed.asset).then((blob) => {
|
|
11244
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
11245
|
+
if (this.disposed) {
|
|
11246
|
+
URL.revokeObjectURL(objectUrl);
|
|
11247
|
+
return null;
|
|
11248
|
+
}
|
|
11249
|
+
this.created.add(objectUrl);
|
|
11250
|
+
return objectUrl;
|
|
11251
|
+
}).catch((error) => {
|
|
11252
|
+
this.pending.delete(cacheKey);
|
|
11253
|
+
throw error;
|
|
11254
|
+
});
|
|
11255
|
+
this.pending.set(cacheKey, task);
|
|
11256
|
+
return task;
|
|
11257
|
+
}
|
|
11258
|
+
/**
|
|
11259
|
+
* Resolve the image fields the synchronous map renderer loads immediately.
|
|
11260
|
+
* View-from-seat media stays lazy: SeatManager does not open that buyer
|
|
11261
|
+
* surface, and eagerly downloading every row panorama would be unbounded.
|
|
11262
|
+
*/
|
|
11263
|
+
async prepareRendererChart(doc) {
|
|
11264
|
+
const prepareBackground = async (background) => {
|
|
11265
|
+
if (!background?.url) return;
|
|
11266
|
+
const resolved = await this.resolve(background.url);
|
|
11267
|
+
if (!resolved) throw new Error("organizer_event_asset_scope_mismatch");
|
|
11268
|
+
background.url = resolved;
|
|
11269
|
+
};
|
|
11270
|
+
const prepareObjects = async (objects) => {
|
|
11271
|
+
for (const object of objects) {
|
|
11272
|
+
if (object.type !== "decorImage") continue;
|
|
11273
|
+
const image = object;
|
|
11274
|
+
const resolved = await this.resolve(image.href);
|
|
11275
|
+
if (!resolved) throw new Error("organizer_event_asset_scope_mismatch");
|
|
11276
|
+
image.href = resolved;
|
|
11277
|
+
}
|
|
11278
|
+
};
|
|
11279
|
+
const prepareOwner = async (owner) => {
|
|
11280
|
+
await prepareBackground(owner.backgroundImage);
|
|
11281
|
+
await prepareObjects(owner.objects);
|
|
11282
|
+
};
|
|
11283
|
+
await prepareOwner(doc);
|
|
11284
|
+
for (const floor of doc.floors ?? []) await prepareOwner(floor);
|
|
11285
|
+
return doc;
|
|
11286
|
+
}
|
|
11287
|
+
dispose() {
|
|
11288
|
+
if (this.disposed) return;
|
|
11289
|
+
this.disposed = true;
|
|
11290
|
+
for (const url of this.created) URL.revokeObjectURL(url);
|
|
11291
|
+
this.created.clear();
|
|
11292
|
+
this.pending.clear();
|
|
11293
|
+
}
|
|
11294
|
+
};
|
|
11295
|
+
|
|
11296
|
+
// src/SeatManager.ts
|
|
10569
11297
|
function availabilityModeOf(rule) {
|
|
10570
11298
|
return rule ? rule.mode : "open";
|
|
10571
11299
|
}
|
|
@@ -10844,8 +11572,8 @@ var MANAGER_CSS = (
|
|
|
10844
11572
|
.slm.compact .slm-modes{min-width:0}.slm.compact .slm-mode{padding-inline:11px}
|
|
10845
11573
|
.slm.compact .slm-live{justify-self:end}.slm.compact .slm-bar-actions{grid-column:1/-1;justify-self:stretch}
|
|
10846
11574
|
.slm.compact .slm-barbtn{flex:1;padding:6px 9px}.slm.compact .slm-kpis{grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}
|
|
10847
|
-
.slm.compact .slm-kpi[data-kpi="
|
|
10848
|
-
.slm.compact .slm-kpi[data-kpi="
|
|
11575
|
+
.slm.compact .slm-kpi[data-kpi="viewing-map"],.slm.compact .slm-kpi[data-kpi="active-holds"],
|
|
11576
|
+
.slm.compact .slm-kpi[data-kpi="booked-pct"],.slm.compact .slm-kpi[data-kpi="booked-value"]{display:none}
|
|
10849
11577
|
/* Reduced motion, as a BLANKET over the cockpit subtree rather than a list of
|
|
10850
11578
|
selectors. The list this replaces named four animations and two transitions,
|
|
10851
11579
|
and had silently fallen behind the stylesheet: the zoom hint, the toast and
|
|
@@ -11055,6 +11783,10 @@ var SeatManager = class {
|
|
|
11055
11783
|
this.currency = options.currency ?? "USD";
|
|
11056
11784
|
this.tokenExpiresAt = options.tokenExpiresAt ?? null;
|
|
11057
11785
|
this.api = new ManageApi(options.apiBase ?? DEFAULT_API_BASE3, options.token);
|
|
11786
|
+
this.organizerAssetUrls = new OrganizerAssetObjectUrls(
|
|
11787
|
+
this.key,
|
|
11788
|
+
(key, asset) => this.withAuthRetry(() => this.api.asset(key, asset))
|
|
11789
|
+
);
|
|
11058
11790
|
this.host = resolveContainer4(options.container);
|
|
11059
11791
|
}
|
|
11060
11792
|
/** Build the DOM, load the chart, subscribe to realtime, mount the board. */
|
|
@@ -11062,10 +11794,10 @@ var SeatManager = class {
|
|
|
11062
11794
|
injectStyle();
|
|
11063
11795
|
this.buildChrome();
|
|
11064
11796
|
try {
|
|
11065
|
-
const res = await this.api.chart(this.key);
|
|
11066
|
-
this.doc = res.doc;
|
|
11797
|
+
const res = await this.withAuthRetry(() => this.api.chart(this.key));
|
|
11798
|
+
this.doc = await this.organizerAssetUrls.prepareRendererChart(res.doc);
|
|
11067
11799
|
this.currency = res.event.currency ?? this.opts.currency ?? this.currency;
|
|
11068
|
-
this.buildUnitUniverse(
|
|
11800
|
+
this.buildUnitUniverse(this.doc);
|
|
11069
11801
|
this.buildRenderer();
|
|
11070
11802
|
this.buildSectionOptions();
|
|
11071
11803
|
const [, controlRoom] = await Promise.all([
|
|
@@ -11412,10 +12144,10 @@ var SeatManager = class {
|
|
|
11412
12144
|
try {
|
|
11413
12145
|
await this.api.unbook(this.key, targets, bookingRef);
|
|
11414
12146
|
this.clearSelection();
|
|
11415
|
-
this.done("cancelBooking", targets, `
|
|
12147
|
+
this.done("cancelBooking", targets, `Released ${targets.length} booked unit${targets.length === 1 ? "" : "s"}.`);
|
|
11416
12148
|
} catch (err) {
|
|
11417
12149
|
this.setSeatsLocal(targets, "booked");
|
|
11418
|
-
this.toastErr("Couldn't
|
|
12150
|
+
this.toastErr("Couldn't release that booked inventory. Check the booking reference.");
|
|
11419
12151
|
this.opts.onError?.(err);
|
|
11420
12152
|
}
|
|
11421
12153
|
}
|
|
@@ -11468,9 +12200,9 @@ var SeatManager = class {
|
|
|
11468
12200
|
async setHoldTtl(ms) {
|
|
11469
12201
|
try {
|
|
11470
12202
|
await this.api.setHoldTtl(this.key, ms);
|
|
11471
|
-
this.done("setHoldTtl", [], ms ? `
|
|
12203
|
+
this.done("setHoldTtl", [], ms ? `Hold window set to ${Math.round(ms / 6e4)} min.` : "Hold window reset.");
|
|
11472
12204
|
} catch (err) {
|
|
11473
|
-
this.toastErr("Couldn't update the
|
|
12205
|
+
this.toastErr("Couldn't update the hold window.");
|
|
11474
12206
|
this.opts.onError?.(err);
|
|
11475
12207
|
}
|
|
11476
12208
|
}
|
|
@@ -11515,6 +12247,7 @@ var SeatManager = class {
|
|
|
11515
12247
|
}
|
|
11516
12248
|
this.renderer?.destroy();
|
|
11517
12249
|
this.renderer = null;
|
|
12250
|
+
this.organizerAssetUrls.dispose();
|
|
11518
12251
|
if (this.root && this.root.parentNode === this.host) this.host.removeChild(this.root);
|
|
11519
12252
|
}
|
|
11520
12253
|
// ---- renderer lifecycle ---------------------------------------------------
|
|
@@ -11635,24 +12368,28 @@ var SeatManager = class {
|
|
|
11635
12368
|
* projects its deltas, so any change inside a private channel allocation is
|
|
11636
12369
|
* structurally suppressed and the map silently drifts.
|
|
11637
12370
|
*
|
|
11638
|
-
* If the mint fails
|
|
11639
|
-
*
|
|
11640
|
-
*
|
|
11641
|
-
* the authenticated HTTP read.
|
|
12371
|
+
* If the mint fails, remain reconnecting. An unticketed socket is a buyer
|
|
12372
|
+
* projection, so applying it to organizer state would be worse than staying
|
|
12373
|
+
* visibly offline while the host refreshes authority or upgrades the API.
|
|
11642
12374
|
*/
|
|
11643
12375
|
async connect() {
|
|
11644
12376
|
if (this.closed) return;
|
|
11645
12377
|
let protocols;
|
|
11646
12378
|
try {
|
|
11647
|
-
protocols = (await this.api.subscribeTicket(this.key)).protocols;
|
|
11648
|
-
|
|
11649
|
-
|
|
12379
|
+
protocols = (await this.withAuthRetry(() => this.api.subscribeTicket(this.key))).protocols;
|
|
12380
|
+
if (!protocols.length) throw new Error("manage_subscribe_ticket_missing");
|
|
12381
|
+
} catch (err) {
|
|
12382
|
+
this.setLive(false);
|
|
12383
|
+
this.opts.onError?.(err);
|
|
12384
|
+
this.scheduleReconnect();
|
|
12385
|
+
return;
|
|
11650
12386
|
}
|
|
11651
12387
|
if (this.closed) return;
|
|
11652
12388
|
let ws;
|
|
11653
12389
|
try {
|
|
11654
|
-
ws =
|
|
11655
|
-
} catch {
|
|
12390
|
+
ws = new WebSocket(this.api.socketUrl(this.key), protocols);
|
|
12391
|
+
} catch (err) {
|
|
12392
|
+
this.opts.onError?.(err);
|
|
11656
12393
|
this.scheduleReconnect();
|
|
11657
12394
|
return;
|
|
11658
12395
|
}
|
|
@@ -11743,8 +12480,9 @@ var SeatManager = class {
|
|
|
11743
12480
|
this.lastSyncedAt = Date.now();
|
|
11744
12481
|
this.afterPaint();
|
|
11745
12482
|
}
|
|
11746
|
-
|
|
11747
|
-
|
|
12483
|
+
const liveBookedValue = typeof m.bookedValue?.gross === "number" ? m.bookedValue.gross : m.revenue?.gross;
|
|
12484
|
+
if (typeof liveBookedValue === "number" && Number.isFinite(liveBookedValue)) {
|
|
12485
|
+
this.applyLiveGross(liveBookedValue);
|
|
11748
12486
|
}
|
|
11749
12487
|
this.recomputeTallies();
|
|
11750
12488
|
}
|
|
@@ -11761,9 +12499,12 @@ var SeatManager = class {
|
|
|
11761
12499
|
this.authoritativeGrossRevenue = gross;
|
|
11762
12500
|
this.revenueStatus = "current";
|
|
11763
12501
|
if (this.controlRoomSnapshot) {
|
|
12502
|
+
const current = this.controlRoomSnapshot.bookedValue ?? this.controlRoomSnapshot.revenue;
|
|
12503
|
+
const bookedValue = { ...current, gross };
|
|
11764
12504
|
this.controlRoomSnapshot = {
|
|
11765
12505
|
...this.controlRoomSnapshot,
|
|
11766
|
-
|
|
12506
|
+
bookedValue,
|
|
12507
|
+
revenue: bookedValue
|
|
11767
12508
|
};
|
|
11768
12509
|
this.opts.onControlRoom?.(this.controlRoomSnapshot);
|
|
11769
12510
|
}
|
|
@@ -11941,7 +12682,10 @@ var SeatManager = class {
|
|
|
11941
12682
|
// ---- tallies + feed -------------------------------------------------------
|
|
11942
12683
|
applyReportRevenue(report) {
|
|
11943
12684
|
this.authoritativeGrossRevenue = report.report.byCategory.reduce(
|
|
11944
|
-
(sum, row) =>
|
|
12685
|
+
(sum, row) => {
|
|
12686
|
+
const value = Number.isFinite(row.bookedValue) ? row.bookedValue : row.bookedRevenue;
|
|
12687
|
+
return sum + (Number.isFinite(value) ? value : 0);
|
|
12688
|
+
},
|
|
11945
12689
|
0
|
|
11946
12690
|
);
|
|
11947
12691
|
this.revenueStatus = "current";
|
|
@@ -11960,7 +12704,13 @@ var SeatManager = class {
|
|
|
11960
12704
|
const requestedAt = Date.now();
|
|
11961
12705
|
try {
|
|
11962
12706
|
const fetched = await this.api.controlRoom(this.key, this.trendWindowMinutes);
|
|
11963
|
-
|
|
12707
|
+
const incoming = fetched.bookedValue ?? fetched.revenue ?? { gross: 0, bySection: [] };
|
|
12708
|
+
const normalizedSections = (incoming.bySection ?? []).map((row) => {
|
|
12709
|
+
const value = Number.isFinite(row.bookedValue) ? row.bookedValue : row.bookedRevenue;
|
|
12710
|
+
return { ...row, bookedValue: value ?? 0, bookedRevenue: value ?? 0 };
|
|
12711
|
+
});
|
|
12712
|
+
const canonical = { ...incoming, bySection: normalizedSections };
|
|
12713
|
+
let snapshot = { ...fetched, bookedValue: canonical, revenue: canonical };
|
|
11964
12714
|
if (request === this.revenueRequest) {
|
|
11965
12715
|
if (this.livePresence && this.livePresence.at >= requestedAt) {
|
|
11966
12716
|
snapshot = { ...snapshot, presence: this.livePresence.value };
|
|
@@ -11968,14 +12718,15 @@ var SeatManager = class {
|
|
|
11968
12718
|
this.livePresence = null;
|
|
11969
12719
|
}
|
|
11970
12720
|
if (this.liveGross && this.liveGross.at >= requestedAt) {
|
|
11971
|
-
|
|
12721
|
+
const bookedValue = { ...snapshot.bookedValue, gross: this.liveGross.value };
|
|
12722
|
+
snapshot = { ...snapshot, bookedValue, revenue: bookedValue };
|
|
11972
12723
|
} else {
|
|
11973
12724
|
this.liveGross = null;
|
|
11974
12725
|
}
|
|
11975
12726
|
this.controlRoomSnapshot = snapshot;
|
|
11976
12727
|
this.rebaseServerTotals(snapshot);
|
|
11977
12728
|
this.lastSyncedAt = Date.now();
|
|
11978
|
-
this.authoritativeGrossRevenue = snapshot.
|
|
12729
|
+
this.authoritativeGrossRevenue = snapshot.bookedValue.gross;
|
|
11979
12730
|
this.currency = snapshot.currency;
|
|
11980
12731
|
this.revenueStatus = "current";
|
|
11981
12732
|
this.recomputeTallies();
|
|
@@ -12033,7 +12784,9 @@ var SeatManager = class {
|
|
|
12033
12784
|
total: Number.isFinite(seatTotal) ? seatTotal : this.unitTotal(),
|
|
12034
12785
|
capacityPct: 0,
|
|
12035
12786
|
sellThroughPct: 0,
|
|
12787
|
+
bookedValue: this.authoritativeGrossRevenue,
|
|
12036
12788
|
grossRevenue: this.authoritativeGrossRevenue,
|
|
12789
|
+
bookedValueStatus: this.revenueStatus,
|
|
12037
12790
|
revenueStatus: this.revenueStatus,
|
|
12038
12791
|
currency: this.currency
|
|
12039
12792
|
};
|
|
@@ -12188,8 +12941,8 @@ var SeatManager = class {
|
|
|
12188
12941
|
<button class="slm-barbtn follow" data-ref="follow" aria-pressed="false"
|
|
12189
12942
|
title="Stay on the current map view unless enabled">Follow live</button>
|
|
12190
12943
|
<button class="slm-barbtn" data-ref="heat" aria-pressed="false"
|
|
12191
|
-
aria-label="
|
|
12192
|
-
title="Highlight sections
|
|
12944
|
+
aria-label="Booking momentum overlay off"
|
|
12945
|
+
title="Highlight sections booking fastest in the selected time window">Booking momentum</button>
|
|
12193
12946
|
<button class="slm-barbtn" data-ref="fullscreen" title="Full screen (F)" aria-keyshortcuts="F">Full screen</button>
|
|
12194
12947
|
</div>
|
|
12195
12948
|
<div class="slm-kpis" data-ref="kpis"></div>
|
|
@@ -12292,16 +13045,16 @@ var SeatManager = class {
|
|
|
12292
13045
|
if (!button) return;
|
|
12293
13046
|
button.classList.toggle("on", this.followLive);
|
|
12294
13047
|
button.setAttribute("aria-pressed", String(this.followLive));
|
|
12295
|
-
button.setAttribute("title", this.followLive ? "Following new
|
|
13048
|
+
button.setAttribute("title", this.followLive ? "Following new holds and bookings. Turn off to keep the current view." : "Stay on the current map view. Enable to follow new holds and bookings.");
|
|
12296
13049
|
}
|
|
12297
13050
|
paintHeatButton() {
|
|
12298
13051
|
const button = this.els.heat;
|
|
12299
13052
|
if (!button) return;
|
|
12300
13053
|
button.classList.toggle("on", this.heatEnabled);
|
|
12301
13054
|
button.setAttribute("aria-pressed", String(this.heatEnabled));
|
|
12302
|
-
button.setAttribute("aria-label", `
|
|
12303
|
-
button.setAttribute("title", `${this.heatEnabled ? "Hide" : "Highlight"} sections
|
|
12304
|
-
button.textContent = "
|
|
13055
|
+
button.setAttribute("aria-label", `Booking momentum overlay ${this.heatEnabled ? "on" : "off"}`);
|
|
13056
|
+
button.setAttribute("title", `${this.heatEnabled ? "Hide" : "Highlight"} sections booking fastest in the selected time window`);
|
|
13057
|
+
button.textContent = "Booking momentum";
|
|
12305
13058
|
this.paintMomentumHelp();
|
|
12306
13059
|
}
|
|
12307
13060
|
paintMomentumHelp() {
|
|
@@ -12345,23 +13098,23 @@ var SeatManager = class {
|
|
|
12345
13098
|
formatKpiDelta(key, delta, currency) {
|
|
12346
13099
|
const sign = delta > 0 ? "+" : "\u2212";
|
|
12347
13100
|
const absolute = Math.abs(delta);
|
|
12348
|
-
if (key === "
|
|
12349
|
-
if (key === "
|
|
13101
|
+
if (key === "booked-value") return `${sign}${fmtMoney(absolute, currency)}`;
|
|
13102
|
+
if (key === "booked-pct") return `${sign}${absolute.toLocaleString()}pt`;
|
|
12350
13103
|
return `${sign}${absolute.toLocaleString()}`;
|
|
12351
13104
|
}
|
|
12352
13105
|
paintKpis(t3) {
|
|
12353
13106
|
if (!this.els.kpis) return;
|
|
12354
|
-
const
|
|
13107
|
+
const bookedValue = t3.bookedValueStatus === "current" ? fmtMoney(t3.bookedValue, t3.currency) : "\u2014";
|
|
12355
13108
|
const presence = this.presenceCounts();
|
|
12356
13109
|
const items = [
|
|
12357
|
-
{ key: "
|
|
12358
|
-
{ key: "held-seats", raw: t3.held, n: t3.held.toLocaleString(), l: "Held
|
|
12359
|
-
{ key: "free-seats", raw: t3.free, n: t3.free.toLocaleString(), l: "
|
|
12360
|
-
{ key: "blocked", raw: t3.blocked, n: t3.blocked.toLocaleString(), l: "Blocked", dot: "#8b94ac", title: "
|
|
12361
|
-
{ key: "
|
|
12362
|
-
{ key: "
|
|
12363
|
-
{ key: "
|
|
12364
|
-
{ key: "
|
|
13110
|
+
{ key: "booked-inventory", raw: t3.booked, n: t3.booked.toLocaleString(), l: "Booked inventory", dot: "#22a06b", title: "Inventory units booked" },
|
|
13111
|
+
{ key: "held-seats", raw: t3.held, n: t3.held.toLocaleString(), l: "Held inventory", dot: "#f4b740", title: "Inventory currently held" },
|
|
13112
|
+
{ key: "free-seats", raw: t3.free, n: t3.free.toLocaleString(), l: "Available", dot: "#6e7bff", title: "Inventory available to book" },
|
|
13113
|
+
{ key: "blocked", raw: t3.blocked, n: t3.blocked.toLocaleString(), l: "Blocked", dot: "#8b94ac", title: "Inventory withheld from booking" },
|
|
13114
|
+
{ key: "viewing-map", raw: presence?.shoppingSessions ?? null, n: presence ? presence.shoppingSessions.toLocaleString() : "\u2014", l: "Viewing map", title: "Active map sessions right now" },
|
|
13115
|
+
{ key: "active-holds", raw: presence?.activeHolds ?? null, n: presence ? presence.activeHolds.toLocaleString() : "\u2014", l: "Active holds", title: "Sessions currently holding inventory" },
|
|
13116
|
+
{ key: "booked-pct", raw: t3.capacityPct, n: `${t3.capacityPct}%`, l: "Booked", title: "Booked inventory as a share of the whole event" },
|
|
13117
|
+
{ key: "booked-value", raw: t3.bookedValueStatus === "current" ? t3.bookedValue : null, n: bookedValue, l: "Booked value", title: "Configured value attached to booked inventory" }
|
|
12365
13118
|
];
|
|
12366
13119
|
let hasChanges = false;
|
|
12367
13120
|
this.els.kpis.innerHTML = items.map((item) => {
|
|
@@ -12410,12 +13163,12 @@ var SeatManager = class {
|
|
|
12410
13163
|
renderViewRail() {
|
|
12411
13164
|
this.els.rail.innerHTML = `
|
|
12412
13165
|
<p class="slm-eyebrow">Monitor</p>
|
|
12413
|
-
<p class="slm-hint">Read-only. Inventory,
|
|
13166
|
+
<p class="slm-hint">Read-only. Inventory, map activity and booking movement update on the same live board.</p>
|
|
12414
13167
|
<div class="slm-health" data-ref="presence"></div>
|
|
12415
13168
|
<div class="slm-legend" data-ref="legend"></div>
|
|
12416
13169
|
<div class="slm-sectionhead">
|
|
12417
|
-
<div><p class="slm-eyebrow">Section
|
|
12418
|
-
<div class="slm-windows" aria-label="
|
|
13170
|
+
<div><p class="slm-eyebrow">Section inventory</p><p class="slm-note">Configured booked value \xB7 booking momentum</p></div>
|
|
13171
|
+
<div class="slm-windows" aria-label="Booking momentum window">
|
|
12419
13172
|
${[5, 15, 30, 60].map((window2) => `<button class="slm-window" data-window="${window2}">${window2}m</button>`).join("")}
|
|
12420
13173
|
</div>
|
|
12421
13174
|
</div>
|
|
@@ -12454,8 +13207,8 @@ var SeatManager = class {
|
|
|
12454
13207
|
const sync = this.lastSyncedAt ? relTime(this.lastSyncedAt, Date.now()) : "waiting";
|
|
12455
13208
|
const presence = this.presenceCounts();
|
|
12456
13209
|
this.els.presence.innerHTML = `
|
|
12457
|
-
<div class="slm-healthitem" title="
|
|
12458
|
-
<div class="slm-healthitem" title="
|
|
13210
|
+
<div class="slm-healthitem" title="Active map sessions right now"><b>${presence ? presence.shoppingSessions.toLocaleString() : "\u2014"}</b><span>Viewing map</span></div>
|
|
13211
|
+
<div class="slm-healthitem" title="Sessions currently holding inventory"><b>${presence ? presence.activeHolds.toLocaleString() : "\u2014"}</b><span>Active holds</span></div>
|
|
12459
13212
|
<div class="slm-healthitem"><b>${connected ? "Healthy" : "Reconnecting"}</b><span>Live connection</span></div>
|
|
12460
13213
|
<div class="slm-healthitem"><b>${sync}</b><span>Last sync</span></div>`;
|
|
12461
13214
|
}
|
|
@@ -12465,10 +13218,10 @@ var SeatManager = class {
|
|
|
12465
13218
|
return;
|
|
12466
13219
|
}
|
|
12467
13220
|
const velocity = new Map(snapshot.velocity.bySection.map((row) => [row.sectionId, row]));
|
|
12468
|
-
const rows = [...snapshot.
|
|
13221
|
+
const rows = [...snapshot.bookedValue.bySection].sort((a, b) => {
|
|
12469
13222
|
const av = velocity.get(a.sectionId)?.netBooked ?? 0;
|
|
12470
13223
|
const bv = velocity.get(b.sectionId)?.netBooked ?? 0;
|
|
12471
|
-
return bv - av || b.
|
|
13224
|
+
return bv - av || b.bookedValue - a.bookedValue;
|
|
12472
13225
|
});
|
|
12473
13226
|
this.els.sections.innerHTML = rows.length ? rows.map((row) => {
|
|
12474
13227
|
const speed = velocity.get(row.sectionId);
|
|
@@ -12476,8 +13229,8 @@ var SeatManager = class {
|
|
|
12476
13229
|
const netLabel = `${net > 0 ? "+" : ""}${net}`;
|
|
12477
13230
|
const trend = speed?.trend === "rising" || speed?.trend === "cooling" ? speed.trend : "steady";
|
|
12478
13231
|
return `<button type="button" class="slm-sectionrow" data-section-focus="${esc2(row.sectionId)}" title="Focus ${esc2(row.sectionLabel)} on the map">
|
|
12479
|
-
<span class="slm-sectiontop"><span>${esc2(row.sectionLabel)}</span><span>${fmtMoney(row.
|
|
12480
|
-
<span class="slm-sectionmeta"><span>${row.booked.toLocaleString()}/${row.total.toLocaleString()}
|
|
13232
|
+
<span class="slm-sectiontop"><span>${esc2(row.sectionLabel)}</span><span>${fmtMoney(row.bookedValue, snapshot.currency)}</span></span>
|
|
13233
|
+
<span class="slm-sectionmeta"><span>${row.booked.toLocaleString()}/${row.total.toLocaleString()} booked \xB7 ${netLabel} in ${snapshot.velocity.windowMinutes}m</span><span class="slm-trend ${trend}">${trend}</span><span class="slm-sectionlocate">Locate</span></span>
|
|
12481
13234
|
</button>`;
|
|
12482
13235
|
}).join("") : '<div class="slm-empty">No section metrics are available for this chart.</div>';
|
|
12483
13236
|
this.paintTrendWindow();
|
|
@@ -12489,7 +13242,7 @@ var SeatManager = class {
|
|
|
12489
13242
|
this.renderer?.setSectionHeat(null);
|
|
12490
13243
|
return;
|
|
12491
13244
|
}
|
|
12492
|
-
const capacity = new Map(snapshot.
|
|
13245
|
+
const capacity = new Map(snapshot.bookedValue.bySection.map((row) => [row.sectionId, Math.max(1, row.total)]));
|
|
12493
13246
|
const rates = snapshot.velocity.bySection.map((row) => ({
|
|
12494
13247
|
sectionId: row.sectionId,
|
|
12495
13248
|
rate: Math.max(0, row.netBooked) / (capacity.get(row.sectionId) ?? 1) / snapshot.velocity.windowMinutes
|
|
@@ -12504,7 +13257,7 @@ var SeatManager = class {
|
|
|
12504
13257
|
if (!seat) {
|
|
12505
13258
|
this.els.rail.innerHTML = `
|
|
12506
13259
|
<p class="slm-eyebrow">Inspect seats</p>
|
|
12507
|
-
<p class="slm-hint">Select a seat to see its availability and
|
|
13260
|
+
<p class="slm-hint">Select a seat to see its availability and booking context. Nothing changes in this view.</p>
|
|
12508
13261
|
<div class="slm-empty">Select a seat on the map.</div>`;
|
|
12509
13262
|
return;
|
|
12510
13263
|
}
|
|
@@ -12513,7 +13266,7 @@ var SeatManager = class {
|
|
|
12513
13266
|
const sectionId = this.sectionByObject.get(seat.rowId) ?? import_core3.UNGROUPED_ID;
|
|
12514
13267
|
const sectionLabel = this.sectionLabelById.get(sectionId) ?? "Other seats";
|
|
12515
13268
|
const category = this.doc?.categories.find((item) => item.key === seat.categoryKey);
|
|
12516
|
-
const sectionMetric = this.controlRoomSnapshot?.
|
|
13269
|
+
const sectionMetric = this.controlRoomSnapshot?.bookedValue.bySection.find((row) => row.sectionId === sectionId);
|
|
12517
13270
|
const object = this.doc?.objects.find((item) => item.id === seat.rowId);
|
|
12518
13271
|
const location2 = object?.type === "row" ? { label: "Row", value: object.label } : object?.type === "table" ? { label: "Table", value: object.label } : seat.kind === "booth" ? { label: "Type", value: "Booth" } : null;
|
|
12519
13272
|
const itemKind = seat.kind === "booth" ? "Booth" : "Seat";
|
|
@@ -12527,8 +13280,8 @@ var SeatManager = class {
|
|
|
12527
13280
|
<div><span>Section</span><b>${esc2(sectionLabel)}</b></div>
|
|
12528
13281
|
${location2 ? `<div><span>${location2.label}</span><b>${esc2(location2.value)}</b></div>` : ""}
|
|
12529
13282
|
<div><span>Category</span><b>${esc2(category?.label ?? seat.categoryKey)}</b></div>
|
|
12530
|
-
<div><span>
|
|
12531
|
-
<div><span>Section
|
|
13283
|
+
<div><span>Booked in section</span><b>${sectionMetric ? `${sectionMetric.booked} of ${sectionMetric.total}` : "\u2014"}</b></div>
|
|
13284
|
+
<div><span>Section booked value</span><b>${sectionMetric && this.controlRoomSnapshot ? fmtMoney(sectionMetric.bookedValue, this.controlRoomSnapshot.currency) : "\u2014"}</b></div>
|
|
12532
13285
|
</div>
|
|
12533
13286
|
</div>`;
|
|
12534
13287
|
}
|
|
@@ -13131,10 +13884,12 @@ init_manageApi();
|
|
|
13131
13884
|
markerOf,
|
|
13132
13885
|
mutationCount,
|
|
13133
13886
|
needsMoveConfirmation,
|
|
13887
|
+
parseTicketOfferAvailability,
|
|
13134
13888
|
planAssignment,
|
|
13135
13889
|
retryAfterCopy,
|
|
13136
13890
|
selectionSources,
|
|
13137
13891
|
stateBadge,
|
|
13138
|
-
suggestMarker
|
|
13892
|
+
suggestMarker,
|
|
13893
|
+
ticketOfferPrices
|
|
13139
13894
|
});
|
|
13140
13895
|
//# sourceMappingURL=index.cjs.map
|