@seatlayer/js 0.41.0 → 0.42.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/dist/index.cjs +178 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -1
- package/dist/index.d.ts +58 -1
- package/dist/index.js +178 -63
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -6901,7 +6901,7 @@ function retryAfterCopy(details) {
|
|
|
6901
6901
|
}
|
|
6902
6902
|
function accessLine(access) {
|
|
6903
6903
|
if (!access || !access.intent) return "\u2014";
|
|
6904
|
-
const base = access.intent === "
|
|
6904
|
+
const base = access.intent === "server" ? "Website integration" : access.intent === "hosted_link" ? "Buyer link" : "Not distributed yet";
|
|
6905
6905
|
const grants = access.hasActiveGrants ? "in use now" : access.lastMintAt ? `last used ${new Date(access.lastMintAt).toLocaleDateString()}` : null;
|
|
6906
6906
|
const detail = access.detail ?? grants;
|
|
6907
6907
|
return detail ? `${base} \xB7 ${detail}` : base;
|
|
@@ -7293,7 +7293,7 @@ var ManageApi = class {
|
|
|
7293
7293
|
};
|
|
7294
7294
|
|
|
7295
7295
|
// src/channelsMode.ts
|
|
7296
|
-
var POLL_MS =
|
|
7296
|
+
var POLL_MS = 3e4;
|
|
7297
7297
|
var MAX_FLAGS = 8;
|
|
7298
7298
|
var SEAT_LIST_PAGE = 300;
|
|
7299
7299
|
var PREVIEW_ELIGIBLE_FILL = "#6e7bff";
|
|
@@ -7392,6 +7392,12 @@ var CHANNELS_CSS = (
|
|
|
7392
7392
|
.slm-ch-selnum.bump{animation:slm-ch-bump var(--slm-mo-base) var(--slm-mo-spring)}
|
|
7393
7393
|
.slm-ch-row2{display:flex;gap:8px;margin-top:8px}
|
|
7394
7394
|
.slm-ch-row2 .slm-btn{flex:1;min-width:0}
|
|
7395
|
+
/* distribute options \u2014 two actions, each with the one sentence that explains it */
|
|
7396
|
+
.slm-ch-dist{display:flex;flex-direction:column;gap:6px;padding:12px;margin-bottom:8px;border:1px solid var(--slm-line);
|
|
7397
|
+
border-radius:10px;background:var(--slm-surface)}
|
|
7398
|
+
.slm-ch-dist b{font-size:13px;font-weight:800}
|
|
7399
|
+
.slm-ch-dist .why{color:var(--slm-muted);font-size:11.5px;line-height:1.45}
|
|
7400
|
+
.slm-ch-dist .slm-btn{width:100%;margin-top:2px}
|
|
7395
7401
|
.slm-ch-alert{display:flex;align-items:flex-start;gap:9px;padding:11px 13px;border-radius:10px;font-size:12.5px;
|
|
7396
7402
|
line-height:1.5;margin-bottom:12px}
|
|
7397
7403
|
.slm-ch-alert.warn{background:rgba(244,183,64,.1);border:1px solid rgba(244,183,64,.4);color:#f4d58a}
|
|
@@ -7407,7 +7413,7 @@ var CHANNELS_CSS = (
|
|
|
7407
7413
|
/* dialogs */
|
|
7408
7414
|
.slm-ch-scrim{position:absolute;inset:0;z-index:12;background:rgba(4,6,12,.62);display:grid;place-items:center;
|
|
7409
7415
|
padding:18px;animation:slm-ch-fade var(--slm-mo-quick) var(--slm-mo-out)}
|
|
7410
|
-
.slm-ch-dialog{width:min(460px,100%);max-height:100%;overflow:auto;background:#12151f;border:1px solid var(--slm-line);
|
|
7416
|
+
.slm-ch-dialog{width:min(460px,100%);max-height:100%;overflow:auto;overscroll-behavior:contain;background:#12151f;border:1px solid var(--slm-line);
|
|
7411
7417
|
border-radius:14px;padding:20px;box-shadow:0 24px 70px rgba(0,0,0,.6);
|
|
7412
7418
|
animation:slm-ch-rise var(--slm-mo-base) var(--slm-mo-out)}
|
|
7413
7419
|
.slm-ch-dialog h3{margin:0 0 4px;font-size:16px;font-weight:800;letter-spacing:-.01em}
|
|
@@ -7434,7 +7440,7 @@ var CHANNELS_CSS = (
|
|
|
7434
7440
|
overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
|
7435
7441
|
.slm-ch-err{color:#f1a4a6;font-size:11.5px;margin-top:6px}
|
|
7436
7442
|
|
|
7437
|
-
/*
|
|
7443
|
+
/* buyer links \u2014 STATUS only; there is no Copy control on this card */
|
|
7438
7444
|
.slm-ch-link{padding:10px 11px;border:1px solid var(--slm-line);border-radius:10px;background:var(--slm-surface);
|
|
7439
7445
|
margin-bottom:8px}
|
|
7440
7446
|
.slm-ch-link .lk-head{display:flex;align-items:center;gap:8px}
|
|
@@ -7453,8 +7459,8 @@ var CHANNELS_CSS = (
|
|
|
7453
7459
|
.slm-ch-radio input{flex:none;margin-top:2px}
|
|
7454
7460
|
.slm-ch-radio b{display:block;font-weight:800;margin-bottom:2px}
|
|
7455
7461
|
.slm-ch-radio .why{display:block;color:var(--slm-muted);font-size:11.5px;line-height:1.45}
|
|
7456
|
-
.slm-ch-seatlist{max-height:44vh;overflow:auto;border:1px solid var(--slm-line);
|
|
7457
|
-
background:var(--slm-surface);margin-top:10px}
|
|
7462
|
+
.slm-ch-seatlist{max-height:44vh;overflow:auto;overscroll-behavior:contain;border:1px solid var(--slm-line);
|
|
7463
|
+
border-radius:10px;background:var(--slm-surface);margin-top:10px}
|
|
7458
7464
|
.slm-ch-seatgroup{padding:8px 10px;border-bottom:1px solid var(--slm-line);display:flex;align-items:center;
|
|
7459
7465
|
justify-content:space-between;gap:8px;font-size:11px;font-weight:800;color:var(--slm-muted);position:sticky;top:0;
|
|
7460
7466
|
background:var(--slm-surface)}
|
|
@@ -7504,12 +7510,12 @@ function bucketRowsHtml(rows) {
|
|
|
7504
7510
|
</div>`).join("");
|
|
7505
7511
|
}
|
|
7506
7512
|
var SERVER_INTEGRATION_HTML = `
|
|
7507
|
-
<p class="slm-eyebrow" style="margin-top:18px">
|
|
7508
|
-
<p class="slm-hint">There is nothing to set up on this screen. Your own server mints a short-lived buyer
|
|
7513
|
+
<p class="slm-eyebrow" style="margin-top:18px">On your website</p>
|
|
7514
|
+
<p class="slm-hint">There is nothing more to set up on this screen. Your own server mints a short-lived buyer
|
|
7509
7515
|
access session for this channel with the SeatLayer server SDK and hands it to the widget. A channel name
|
|
7510
7516
|
on its own never grants access.</p>
|
|
7511
7517
|
<p class="slm-note"><a class="slm-linkbtn" href="https://docs.seatlayer.io/server-api/channels"
|
|
7512
|
-
target="_blank" rel="noreferrer noopener">Read the
|
|
7518
|
+
target="_blank" rel="noreferrer noopener">Read the website integration guide \u2192</a></p>`;
|
|
7513
7519
|
function esc(value) {
|
|
7514
7520
|
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
7515
7521
|
}
|
|
@@ -7590,6 +7596,17 @@ var ChannelsMode = class {
|
|
|
7590
7596
|
this.stagedDoneTimer = null;
|
|
7591
7597
|
this.lastSelectionCount = 0;
|
|
7592
7598
|
this.lastCounts = /* @__PURE__ */ new Map();
|
|
7599
|
+
/** The markup currently in the rail. An identical repaint is skipped, which is
|
|
7600
|
+
* what keeps the organizer's scroll position (and open <select>) alive. */
|
|
7601
|
+
this.railHtml = null;
|
|
7602
|
+
/**
|
|
7603
|
+
* The `assignmentVersion` the allocation map was built from. Walking every
|
|
7604
|
+
* allocation page is the expensive half of a refresh and the server already
|
|
7605
|
+
* tells us, in the channels response, whether ANY seat moved. Unchanged
|
|
7606
|
+
* version, unchanged allocation — so the walk is skipped entirely.
|
|
7607
|
+
*/
|
|
7608
|
+
this.allocationVersion = null;
|
|
7609
|
+
this.onVisibility = null;
|
|
7593
7610
|
this.host = host;
|
|
7594
7611
|
this.caps = capabilities;
|
|
7595
7612
|
}
|
|
@@ -7600,6 +7617,7 @@ var ChannelsMode = class {
|
|
|
7600
7617
|
this.active = true;
|
|
7601
7618
|
this.mapIntent = "pan";
|
|
7602
7619
|
this.focusedSectionId = null;
|
|
7620
|
+
this.railHtml = null;
|
|
7603
7621
|
this.ensureLayer();
|
|
7604
7622
|
this.host.root.classList.add("ch-mode");
|
|
7605
7623
|
this.applySheetClasses();
|
|
@@ -7608,8 +7626,15 @@ var ChannelsMode = class {
|
|
|
7608
7626
|
this.onInteractionChange?.();
|
|
7609
7627
|
void this.refresh();
|
|
7610
7628
|
this.pollTimer = setInterval(() => {
|
|
7629
|
+
if (typeof document !== "undefined" && document.hidden) return;
|
|
7611
7630
|
void this.refresh({ quiet: true });
|
|
7612
7631
|
}, POLL_MS);
|
|
7632
|
+
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
7633
|
+
this.onVisibility = () => {
|
|
7634
|
+
if (this.active && !document.hidden) void this.refresh({ quiet: true });
|
|
7635
|
+
};
|
|
7636
|
+
document.addEventListener("visibilitychange", this.onVisibility);
|
|
7637
|
+
}
|
|
7613
7638
|
}
|
|
7614
7639
|
/** Called when the cockpit leaves Channels mode. Everything this mode painted
|
|
7615
7640
|
* over the map goes with it — no other tool ever inherits a channel overlay. */
|
|
@@ -7618,6 +7643,11 @@ var ChannelsMode = class {
|
|
|
7618
7643
|
this.active = false;
|
|
7619
7644
|
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
7620
7645
|
this.pollTimer = null;
|
|
7646
|
+
if (this.onVisibility && typeof document !== "undefined") {
|
|
7647
|
+
document.removeEventListener("visibilitychange", this.onVisibility);
|
|
7648
|
+
}
|
|
7649
|
+
this.onVisibility = null;
|
|
7650
|
+
this.railHtml = null;
|
|
7621
7651
|
this.closeDialog({ restoreFocus: false });
|
|
7622
7652
|
this.links = [];
|
|
7623
7653
|
this.linksChannelId = null;
|
|
@@ -7706,8 +7736,11 @@ var ChannelsMode = class {
|
|
|
7706
7736
|
if (!this.targetChannelId) {
|
|
7707
7737
|
this.targetChannelId = list.channels.find((c) => c.state === "active")?.id ?? PUBLIC_CHANNEL_ID;
|
|
7708
7738
|
}
|
|
7709
|
-
|
|
7710
|
-
|
|
7739
|
+
if (this.allocationVersion !== list.assignmentVersion) {
|
|
7740
|
+
await this.loadAllocation(seq);
|
|
7741
|
+
if (superseded()) return;
|
|
7742
|
+
this.allocationVersion = list.assignmentVersion;
|
|
7743
|
+
}
|
|
7711
7744
|
if (this.detailChannelId) await this.loadLinks(this.detailChannelId);
|
|
7712
7745
|
if (superseded()) return;
|
|
7713
7746
|
this.loading = false;
|
|
@@ -8126,35 +8159,59 @@ var ChannelsMode = class {
|
|
|
8126
8159
|
this.paintOverlay();
|
|
8127
8160
|
}
|
|
8128
8161
|
}
|
|
8162
|
+
/**
|
|
8163
|
+
* Replace the rail's markup — but only when it actually differs, and never at
|
|
8164
|
+
* the cost of where the organizer had scrolled to.
|
|
8165
|
+
*
|
|
8166
|
+
* The rail repaints on a clock, on every selection change and after every
|
|
8167
|
+
* mutation. Rewriting `innerHTML` each time resets `scrollTop`, which is
|
|
8168
|
+
* exactly what "the rail gets stuck" was: scroll down to Create channel, the
|
|
8169
|
+
* poll ticks, and the list snaps back to the top under the cursor. So skip the
|
|
8170
|
+
* write when the markup is byte-identical, and restore the offset when it is
|
|
8171
|
+
* not.
|
|
8172
|
+
*
|
|
8173
|
+
* Returns whether the DOM was rewritten. Callers must only re-wire listeners
|
|
8174
|
+
* when it was — a skipped paint keeps the old nodes AND their listeners, so
|
|
8175
|
+
* re-wiring would double every handler.
|
|
8176
|
+
*/
|
|
8177
|
+
setRailHtml(html) {
|
|
8178
|
+
if (html === this.railHtml) return false;
|
|
8179
|
+
const rail = this.host.rail;
|
|
8180
|
+
const scrollTop = rail.scrollTop;
|
|
8181
|
+
rail.innerHTML = html;
|
|
8182
|
+
this.railHtml = html;
|
|
8183
|
+
if (scrollTop) rail.scrollTop = scrollTop;
|
|
8184
|
+
return true;
|
|
8185
|
+
}
|
|
8129
8186
|
paintRail() {
|
|
8130
8187
|
if (!this.active) return;
|
|
8131
8188
|
const rail = this.host.rail;
|
|
8132
8189
|
if (!this.caps.view) {
|
|
8133
|
-
|
|
8134
|
-
<p class="slm-hint">You need channel-management permission on this event to see allocations.</p
|
|
8190
|
+
this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
|
|
8191
|
+
<p class="slm-hint">You need channel-management permission on this event to see allocations.</p>`);
|
|
8135
8192
|
return;
|
|
8136
8193
|
}
|
|
8137
8194
|
if (this.loading && !this.list) {
|
|
8138
|
-
|
|
8139
|
-
<div class="slm-empty">Loading allocations\u2026</div
|
|
8195
|
+
this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
|
|
8196
|
+
<div class="slm-empty">Loading allocations\u2026</div>`);
|
|
8140
8197
|
return;
|
|
8141
8198
|
}
|
|
8142
8199
|
if (!this.list && this.loadError) {
|
|
8143
|
-
|
|
8200
|
+
if (this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
|
|
8144
8201
|
<div class="slm-ch-alert err" role="alert"><span>\u26A0</span>
|
|
8145
8202
|
<span><b>Couldn't load sales channels.</b> Everything else on this event still works.
|
|
8146
|
-
<button type="button" data-ch-act="retry">Try again</button></span></div
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8203
|
+
<button type="button" data-ch-act="retry">Try again</button></span></div>`)) {
|
|
8204
|
+
rail.querySelector('[data-ch-act="retry"]')?.addEventListener("click", () => {
|
|
8205
|
+
void this.refresh();
|
|
8206
|
+
});
|
|
8207
|
+
}
|
|
8150
8208
|
return;
|
|
8151
8209
|
}
|
|
8152
8210
|
const selection = this.host.selectionLabels();
|
|
8153
8211
|
const grab = this.host.isCompact() ? `<div class="slm-ch-grab"><span class="slm-ch-grabbar"></span></div>` : "";
|
|
8154
8212
|
const segment = this.viewSegmentHtml();
|
|
8155
8213
|
const body = this.view === "preview" ? this.previewRailHtml() : this.detailChannelId ? this.detailRailHtml(this.detailChannelId) : selection.length && this.caps.manage ? this.selectionRailHtml(selection) : this.listRailHtml();
|
|
8156
|
-
|
|
8157
|
-
this.wireRail();
|
|
8214
|
+
if (this.setRailHtml(`${grab}${segment}${body}`)) this.wireRail();
|
|
8158
8215
|
this.paintStagedBar();
|
|
8159
8216
|
}
|
|
8160
8217
|
viewSegmentHtml() {
|
|
@@ -8285,22 +8342,7 @@ var ChannelsMode = class {
|
|
|
8285
8342
|
<button type="button" class="slm-btn ghost" data-ch-act="archive">Archive\u2026</button>
|
|
8286
8343
|
</div>
|
|
8287
8344
|
<p class="slm-note">Archive returns the allocation to a destination you choose. Nothing is ever deleted silently.</p>` : "";
|
|
8288
|
-
const
|
|
8289
|
-
const intents = ["none", "internal", "server", "hosted_link"];
|
|
8290
|
-
const selfServiceGap = (intent === "server" || intent === "hosted_link") && !channel.access?.hasActiveGrants;
|
|
8291
|
-
const access = this.caps.manage ? `
|
|
8292
|
-
<p class="slm-eyebrow" style="margin-top:14px">Buyer access</p>
|
|
8293
|
-
<div class="slm-field">
|
|
8294
|
-
<label for="slm-ch-intent">How should buyers reach this channel?</label>
|
|
8295
|
-
<select class="slm-select" id="slm-ch-intent" data-ch-intent>
|
|
8296
|
-
${intents.map((value) => `<option value="${value}"${value === intent ? " selected" : ""}>${esc(accessIntentLabel(value))}</option>`).join("")}
|
|
8297
|
-
</select>
|
|
8298
|
-
</div>
|
|
8299
|
-
<p class="slm-hint">${channel.access?.hasActiveGrants ? "Buyer access is live. Only this audience can buy from the allocation." : "No buyer access is configured yet. The allocation is protected \u2014 it is not available to Public sale."}</p>
|
|
8300
|
-
${selfServiceGap ? `<div class="slm-ch-alert warn"><span>\u26A0</span>
|
|
8301
|
-
<span>This channel is marked for buyer self-service but no buyer has been let in yet.</span></div>` : ""}
|
|
8302
|
-
${this.hostedLinksHtml()}
|
|
8303
|
-
${SERVER_INTEGRATION_HTML}` : "";
|
|
8345
|
+
const access = this.caps.manage ? this.distributeHtml(channel) : "";
|
|
8304
8346
|
return `
|
|
8305
8347
|
<p class="slm-eyebrow">
|
|
8306
8348
|
<button type="button" class="slm-linkbtn" data-ch-act="back" style="text-align:left">\u2039 All channels</button>
|
|
@@ -8310,7 +8352,54 @@ var ChannelsMode = class {
|
|
|
8310
8352
|
${access}
|
|
8311
8353
|
${lifecycle}`;
|
|
8312
8354
|
}
|
|
8313
|
-
|
|
8355
|
+
/**
|
|
8356
|
+
* "Distribute" — how the seats in this channel actually reach a buyer.
|
|
8357
|
+
*
|
|
8358
|
+
* This replaced a four-value "access intent" picker (none / internal /
|
|
8359
|
+
* hosted_link / server). Two of those values did nothing anywhere: no buyer or
|
|
8360
|
+
* inventory path reads `access_intent`, so `none` and `internal` were labels an
|
|
8361
|
+
* organizer could set and then wait forever for something to happen. A third,
|
|
8362
|
+
* `hosted_link`, is not the organizer's to choose at all — the server sets it
|
|
8363
|
+
* when a buyer link is created and clears it when the last live one is revoked.
|
|
8364
|
+
*
|
|
8365
|
+
* So this is two ACTIONS, not a setting: create a buyer link, or point the
|
|
8366
|
+
* channel at a website integration. Both of them do something the moment they
|
|
8367
|
+
* are pressed. A legacy row still carrying `none` or `internal` renders the
|
|
8368
|
+
* neutral "not distributed yet" state with both actions offered — the stored
|
|
8369
|
+
* value is left alone (it is organizer-declared metadata and the API that
|
|
8370
|
+
* writes it is unchanged), it simply no longer has a control of its own.
|
|
8371
|
+
*/
|
|
8372
|
+
distributeHtml(channel) {
|
|
8373
|
+
const intent = channel.access?.intent ?? "none";
|
|
8374
|
+
const live = channel.access?.hasActiveGrants === true;
|
|
8375
|
+
const state = live ? intent === "server" ? "Your website is letting buyers in. Only they can buy these seats." : "A buyer link is live. Only people with that link can buy these seats." : intent === "server" ? "Set up for your website \u2014 no buyer has come through yet. Seats stay reserved." : "Not distributed yet \u2014 seats stay reserved.";
|
|
8376
|
+
const option = (title, why, action, cta, primary) => `<div class="slm-ch-dist">
|
|
8377
|
+
<b>${esc(title)}</b>
|
|
8378
|
+
<span class="why">${esc(why)}</span>
|
|
8379
|
+
<button type="button" class="slm-btn${primary ? "" : " ghost"}" data-ch-act="${esc(action)}">${esc(cta)}</button>
|
|
8380
|
+
</div>`;
|
|
8381
|
+
const linksSupported = this.linksState !== "unsupported";
|
|
8382
|
+
return `
|
|
8383
|
+
<p class="slm-eyebrow" style="margin-top:14px">Distribute</p>
|
|
8384
|
+
<p class="slm-hint">${esc(state)}</p>
|
|
8385
|
+
${linksSupported ? option(
|
|
8386
|
+
"Sell with a buyer link",
|
|
8387
|
+
"SeatLayer makes a link you send to a named group. They open it and buy only these seats.",
|
|
8388
|
+
"link-create",
|
|
8389
|
+
this.links.some(accessLinkIsLive) ? "Create another buyer link" : "Create buyer link",
|
|
8390
|
+
true
|
|
8391
|
+
) : ""}
|
|
8392
|
+
${option(
|
|
8393
|
+
"Integrate a website or app",
|
|
8394
|
+
"Your website's backend grants each buyer access \u2014 set up on the Embed page.",
|
|
8395
|
+
"embed-code",
|
|
8396
|
+
"Get embed code",
|
|
8397
|
+
false
|
|
8398
|
+
)}
|
|
8399
|
+
${this.hostedLinksHtml()}
|
|
8400
|
+
${intent === "server" ? SERVER_INTEGRATION_HTML : ""}`;
|
|
8401
|
+
}
|
|
8402
|
+
// ---- buyer links ----------------------------------------------------------
|
|
8314
8403
|
/**
|
|
8315
8404
|
* Read the status projection for the open channel. Never paints — the caller
|
|
8316
8405
|
* decides when the rail repaints, so a poll-driven reload does not fight a
|
|
@@ -8340,32 +8429,34 @@ var ChannelsMode = class {
|
|
|
8340
8429
|
}
|
|
8341
8430
|
}
|
|
8342
8431
|
/**
|
|
8343
|
-
* The
|
|
8432
|
+
* The buyer-link status section of the detail panel.
|
|
8344
8433
|
*
|
|
8345
8434
|
* STATUS ONLY, by design (comp 06 `hosted`): label, state, expiry,
|
|
8346
8435
|
* redemptions, seats per buyer, live sessions. There is no Copy control here
|
|
8347
8436
|
* and no field to hang one on — the URL was shown once at creation and cannot
|
|
8348
8437
|
* be produced again. Rotation is the recovery path, and it says so.
|
|
8438
|
+
*
|
|
8439
|
+
* Creating a link is the Distribute card's action, not this section's, so a
|
|
8440
|
+
* channel with no links renders nothing here rather than an empty heading and
|
|
8441
|
+
* a second button saying the same thing.
|
|
8349
8442
|
*/
|
|
8350
8443
|
hostedLinksHtml() {
|
|
8351
|
-
const eyebrow = `<p class="slm-eyebrow" style="margin-top:18px">
|
|
8444
|
+
const eyebrow = `<p class="slm-eyebrow" style="margin-top:18px">Buyer links</p>`;
|
|
8352
8445
|
if (this.linksState === "unsupported") {
|
|
8353
8446
|
return `${eyebrow}<div class="slm-ch-alert warn"><span>\u2139</span>
|
|
8354
|
-
<span><b>
|
|
8447
|
+
<span><b>Buyer links need a newer server.</b> Everything else on this channel works normally.</span></div>`;
|
|
8355
8448
|
}
|
|
8356
8449
|
if (this.linksState === "error") {
|
|
8357
8450
|
return `${eyebrow}<div class="slm-ch-alert err" role="alert"><span>\u26A0</span>
|
|
8358
8451
|
<span><b>Couldn't load this channel's links.</b>
|
|
8359
8452
|
<button type="button" data-ch-act="link-reload">Try again</button></span></div>`;
|
|
8360
8453
|
}
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
${live ? "Create another hosted link" : "Create hosted access link"}</button>` : "";
|
|
8454
|
+
if (this.linksState === "loading" && !this.links.length) {
|
|
8455
|
+
return `${eyebrow}<div class="slm-empty">Loading links\u2026</div>`;
|
|
8456
|
+
}
|
|
8457
|
+
if (!this.links.length) return "";
|
|
8366
8458
|
return `${eyebrow}
|
|
8367
|
-
${
|
|
8368
|
-
${create}
|
|
8459
|
+
${this.links.map((link) => this.linkCardHtml(link)).join("")}
|
|
8369
8460
|
<p class="slm-note">A link is shown once, when you create it. SeatLayer keeps only a fingerprint of it, so it
|
|
8370
8461
|
can never be shown again \u2014 if a link is lost, rotate it and send the fresh one.</p>`;
|
|
8371
8462
|
}
|
|
@@ -8384,7 +8475,7 @@ var ChannelsMode = class {
|
|
|
8384
8475
|
</div>` : "";
|
|
8385
8476
|
return `<div class="slm-ch-link">
|
|
8386
8477
|
<span class="lk-head">
|
|
8387
|
-
<span class="lk-name">${esc(link.label || "
|
|
8478
|
+
<span class="lk-name">${esc(link.label || "Buyer link")}</span>
|
|
8388
8479
|
<span class="slm-ch-badge ${badge.kind}">${esc(badge.text)}</span>
|
|
8389
8480
|
</span>
|
|
8390
8481
|
<div class="slm-ch-meter" role="img"
|
|
@@ -8487,10 +8578,6 @@ var ChannelsMode = class {
|
|
|
8487
8578
|
this.previewIncludePublic = includePublic.checked;
|
|
8488
8579
|
void this.loadPreview();
|
|
8489
8580
|
});
|
|
8490
|
-
const intent = rail.querySelector("[data-ch-intent]");
|
|
8491
|
-
intent?.addEventListener("change", () => {
|
|
8492
|
-
void this.setAccessIntent(intent.value);
|
|
8493
|
-
});
|
|
8494
8581
|
const grab = rail.querySelector(".slm-ch-grab");
|
|
8495
8582
|
grab?.addEventListener("click", () => this.cycleDetent());
|
|
8496
8583
|
rail.querySelectorAll("[data-ch-act]").forEach((button) => {
|
|
@@ -8535,6 +8622,13 @@ var ChannelsMode = class {
|
|
|
8535
8622
|
case "link-create":
|
|
8536
8623
|
this.openDialog({ kind: "linkCreate", channelId: this.detailChannelId });
|
|
8537
8624
|
break;
|
|
8625
|
+
// The one thing this screen can actually do for a website integration is
|
|
8626
|
+
// record that the channel is meant for one — which is exactly the flag the
|
|
8627
|
+
// dashboard's Embed page reads before it offers the snippet. Saying so is
|
|
8628
|
+
// honest; a fake "copy code" button on a screen with no code would not be.
|
|
8629
|
+
case "embed-code":
|
|
8630
|
+
void this.chooseWebsiteIntegration();
|
|
8631
|
+
break;
|
|
8538
8632
|
case "link-reload":
|
|
8539
8633
|
if (this.detailChannelId) void this.reloadLinks();
|
|
8540
8634
|
break;
|
|
@@ -8875,12 +8969,27 @@ var ChannelsMode = class {
|
|
|
8875
8969
|
});
|
|
8876
8970
|
});
|
|
8877
8971
|
}
|
|
8972
|
+
/**
|
|
8973
|
+
* "Get embed code" — mark the channel as reached through the organizer's own
|
|
8974
|
+
* backend. The write itself is `setChannelAccessIntent(…, 'server')`, the same
|
|
8975
|
+
* API the old picker called; the difference is that it is now a deliberate
|
|
8976
|
+
* action with a consequence the organizer is told about, rather than one of
|
|
8977
|
+
* four dropdown values with no observable effect.
|
|
8978
|
+
*/
|
|
8979
|
+
async chooseWebsiteIntegration() {
|
|
8980
|
+
const channelId = this.detailChannelId;
|
|
8981
|
+
if (!channelId) return;
|
|
8982
|
+
await this.setAccessIntent("server");
|
|
8983
|
+
}
|
|
8878
8984
|
async setAccessIntent(accessIntent) {
|
|
8879
8985
|
const channelId = this.detailChannelId;
|
|
8880
8986
|
if (!channelId || !this.caps.manage) return;
|
|
8881
8987
|
try {
|
|
8882
8988
|
await this.host.api.setChannelAccessIntent(this.host.eventKey, channelId, accessIntent);
|
|
8883
8989
|
await this.refresh();
|
|
8990
|
+
if (accessIntent === "server") {
|
|
8991
|
+
this.host.toast("Marked for your website. The embed code is on the Embed page.", "ok");
|
|
8992
|
+
}
|
|
8884
8993
|
} catch (err) {
|
|
8885
8994
|
this.host.toast("Couldn't save how buyers reach this channel.", "err");
|
|
8886
8995
|
this.host.onError(err);
|
|
@@ -9021,7 +9130,7 @@ var ChannelsMode = class {
|
|
|
9021
9130
|
});
|
|
9022
9131
|
});
|
|
9023
9132
|
}
|
|
9024
|
-
// ----
|
|
9133
|
+
// ---- buyer-link dialogs ---------------------------------------------------
|
|
9025
9134
|
async reloadLinks() {
|
|
9026
9135
|
const channelId = this.detailChannelId;
|
|
9027
9136
|
if (!channelId) return;
|
|
@@ -9064,7 +9173,7 @@ var ChannelsMode = class {
|
|
|
9064
9173
|
return;
|
|
9065
9174
|
}
|
|
9066
9175
|
this.renderScrim(`
|
|
9067
|
-
<h3 id="slm-ch-dlg-title">Create a
|
|
9176
|
+
<h3 id="slm-ch-dlg-title">Create a buyer link for ${esc(channel.name)}</h3>
|
|
9068
9177
|
<p class="sub">Anyone who opens the link can buy from this channel's allocation \u2014 and only from it.
|
|
9069
9178
|
You'll see the link once, right after you create it.</p>
|
|
9070
9179
|
<div class="slm-field">
|
|
@@ -9189,7 +9298,7 @@ var ChannelsMode = class {
|
|
|
9189
9298
|
copy?.addEventListener("click", () => {
|
|
9190
9299
|
const ok = () => {
|
|
9191
9300
|
copy.textContent = "Copied";
|
|
9192
|
-
this.announce("
|
|
9301
|
+
this.announce("Buyer link copied.");
|
|
9193
9302
|
};
|
|
9194
9303
|
const clipboard = typeof navigator === "undefined" ? null : navigator.clipboard;
|
|
9195
9304
|
if (clipboard?.writeText) {
|
|
@@ -9199,7 +9308,7 @@ var ChannelsMode = class {
|
|
|
9199
9308
|
selectSecret(dialog);
|
|
9200
9309
|
});
|
|
9201
9310
|
});
|
|
9202
|
-
this.announce("Your
|
|
9311
|
+
this.announce("Your buyer link is ready and is shown once.");
|
|
9203
9312
|
}
|
|
9204
9313
|
/**
|
|
9205
9314
|
* Rotate. The organizer must SAY what happens to the buyers already inside —
|
|
@@ -9218,7 +9327,7 @@ var ChannelsMode = class {
|
|
|
9218
9327
|
<span><b>${sessions.toLocaleString()} buyer${sessions === 1 ? "" : "s"}</b> got in with the current link
|
|
9219
9328
|
and still ${sessions === 1 ? "has" : "have"} active access.</span></div>` : "";
|
|
9220
9329
|
this.renderScrim(`
|
|
9221
|
-
<h3 id="slm-ch-dlg-title">Rotate the ${esc(link.label || "
|
|
9330
|
+
<h3 id="slm-ch-dlg-title">Rotate the ${esc(link.label || "buyer")} link?</h3>
|
|
9222
9331
|
<p class="sub">The current link stops opening immediately and cannot be restored. You will get a new
|
|
9223
9332
|
link to copy \u2014 shown once.</p>
|
|
9224
9333
|
${warning}
|
|
@@ -9277,7 +9386,7 @@ var ChannelsMode = class {
|
|
|
9277
9386
|
}
|
|
9278
9387
|
const sessions = link.activeSessions ?? 0;
|
|
9279
9388
|
this.renderScrim(`
|
|
9280
|
-
<h3 id="slm-ch-dlg-title">Revoke the ${esc(link.label || "
|
|
9389
|
+
<h3 id="slm-ch-dlg-title">Revoke the ${esc(link.label || "buyer")} link?</h3>
|
|
9281
9390
|
<p class="sub">It stops opening immediately and cannot be restored \u2014 there is no undo, and no way to
|
|
9282
9391
|
bring the same URL back. Seats already bought through it keep their sale.</p>
|
|
9283
9392
|
${sessions ? `<label class="slm-ch-radio">
|
|
@@ -9394,7 +9503,12 @@ var LEGEND = [
|
|
|
9394
9503
|
var MANAGER_CSS = (
|
|
9395
9504
|
/* @sl-css */
|
|
9396
9505
|
`
|
|
9397
|
-
|
|
9506
|
+
/* The floor was 480px, which trapped the cockpit in any host shorter than that:
|
|
9507
|
+
every ancestor here is overflow:hidden, so the bottom of the rail (Create
|
|
9508
|
+
channel, Show archived) was rendered but clipped away by the host and could
|
|
9509
|
+
not be reached by scrolling anything. 320px still keeps an unsized host from
|
|
9510
|
+
collapsing to the top bar, and the rail scrolls to its full content below. */
|
|
9511
|
+
.slm{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:320px;overflow:hidden;
|
|
9398
9512
|
background:var(--slm-bg);color:var(--slm-text);font-family:var(--slm-font);border-radius:var(--slm-radius);
|
|
9399
9513
|
/* Motion tokens (motion-system \xA72), declared by the cockpit ROOT rather than
|
|
9400
9514
|
borrowed from CHANNELS_CSS. The base cockpit animates whether or not
|
|
@@ -9462,7 +9576,7 @@ var MANAGER_CSS = (
|
|
|
9462
9576
|
.slm-liveeventdot{width:8px;height:8px;border-radius:50%;flex:none}.slm-liveeventcopy{min-width:0;overflow:hidden;text-overflow:ellipsis;
|
|
9463
9577
|
white-space:nowrap;font-size:12px;font-weight:800}.slm-liveeventhint{color:var(--slm-muted);font-size:10px;white-space:nowrap}
|
|
9464
9578
|
.slm-rail{width:320px;flex:none;border-left:1px solid var(--slm-line);display:flex;flex-direction:column;min-height:0}
|
|
9465
|
-
.slm-railscroll{flex:1;overflow-y:auto;padding:16px}
|
|
9579
|
+
.slm-railscroll{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px}
|
|
9466
9580
|
.slm-eyebrow{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--slm-muted);font-weight:800;margin-bottom:6px}
|
|
9467
9581
|
.slm-hint{font-size:12.5px;color:var(--slm-muted);line-height:1.5;margin-bottom:14px}
|
|
9468
9582
|
|
|
@@ -9527,7 +9641,8 @@ var MANAGER_CSS = (
|
|
|
9527
9641
|
color:var(--slm-muted);font-size:10.5px}
|
|
9528
9642
|
.slm-linkbtn{font-size:11px!important;font-weight:800!important;color:var(--slm-accent)!important;text-align:right}
|
|
9529
9643
|
.slm-linkbtn:disabled{opacity:.45;cursor:not-allowed}
|
|
9530
|
-
.slm-blockedlist{max-height:246px;overflow:auto;border:1px solid var(--slm-line);
|
|
9644
|
+
.slm-blockedlist{max-height:246px;overflow:auto;overscroll-behavior:contain;border:1px solid var(--slm-line);
|
|
9645
|
+
border-radius:10px;background:var(--slm-surface)}
|
|
9531
9646
|
.slm-blockeditem{display:grid!important;grid-template-columns:18px minmax(0,1fr);width:100%;gap:8px;padding:8px 9px!important;
|
|
9532
9647
|
border-bottom:1px solid var(--slm-line)!important;text-align:left!important}
|
|
9533
9648
|
.slm-blockeditem:last-child{border-bottom:0!important}.slm-blockeditem:hover{background:rgba(255,255,255,.035)!important}
|