@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.js
CHANGED
|
@@ -6493,7 +6493,7 @@ function retryAfterCopy(details) {
|
|
|
6493
6493
|
}
|
|
6494
6494
|
function accessLine(access) {
|
|
6495
6495
|
if (!access || !access.intent) return "\u2014";
|
|
6496
|
-
const base = access.intent === "
|
|
6496
|
+
const base = access.intent === "server" ? "Website integration" : access.intent === "hosted_link" ? "Buyer link" : "Not distributed yet";
|
|
6497
6497
|
const grants = access.hasActiveGrants ? "in use now" : access.lastMintAt ? `last used ${new Date(access.lastMintAt).toLocaleDateString()}` : null;
|
|
6498
6498
|
const detail = access.detail ?? grants;
|
|
6499
6499
|
return detail ? `${base} \xB7 ${detail}` : base;
|
|
@@ -6885,7 +6885,7 @@ var ManageApi = class {
|
|
|
6885
6885
|
};
|
|
6886
6886
|
|
|
6887
6887
|
// src/channelsMode.ts
|
|
6888
|
-
var POLL_MS =
|
|
6888
|
+
var POLL_MS = 3e4;
|
|
6889
6889
|
var MAX_FLAGS = 8;
|
|
6890
6890
|
var SEAT_LIST_PAGE = 300;
|
|
6891
6891
|
var PREVIEW_ELIGIBLE_FILL = "#6e7bff";
|
|
@@ -6984,6 +6984,12 @@ var CHANNELS_CSS = (
|
|
|
6984
6984
|
.slm-ch-selnum.bump{animation:slm-ch-bump var(--slm-mo-base) var(--slm-mo-spring)}
|
|
6985
6985
|
.slm-ch-row2{display:flex;gap:8px;margin-top:8px}
|
|
6986
6986
|
.slm-ch-row2 .slm-btn{flex:1;min-width:0}
|
|
6987
|
+
/* distribute options \u2014 two actions, each with the one sentence that explains it */
|
|
6988
|
+
.slm-ch-dist{display:flex;flex-direction:column;gap:6px;padding:12px;margin-bottom:8px;border:1px solid var(--slm-line);
|
|
6989
|
+
border-radius:10px;background:var(--slm-surface)}
|
|
6990
|
+
.slm-ch-dist b{font-size:13px;font-weight:800}
|
|
6991
|
+
.slm-ch-dist .why{color:var(--slm-muted);font-size:11.5px;line-height:1.45}
|
|
6992
|
+
.slm-ch-dist .slm-btn{width:100%;margin-top:2px}
|
|
6987
6993
|
.slm-ch-alert{display:flex;align-items:flex-start;gap:9px;padding:11px 13px;border-radius:10px;font-size:12.5px;
|
|
6988
6994
|
line-height:1.5;margin-bottom:12px}
|
|
6989
6995
|
.slm-ch-alert.warn{background:rgba(244,183,64,.1);border:1px solid rgba(244,183,64,.4);color:#f4d58a}
|
|
@@ -6999,7 +7005,7 @@ var CHANNELS_CSS = (
|
|
|
6999
7005
|
/* dialogs */
|
|
7000
7006
|
.slm-ch-scrim{position:absolute;inset:0;z-index:12;background:rgba(4,6,12,.62);display:grid;place-items:center;
|
|
7001
7007
|
padding:18px;animation:slm-ch-fade var(--slm-mo-quick) var(--slm-mo-out)}
|
|
7002
|
-
.slm-ch-dialog{width:min(460px,100%);max-height:100%;overflow:auto;background:#12151f;border:1px solid var(--slm-line);
|
|
7008
|
+
.slm-ch-dialog{width:min(460px,100%);max-height:100%;overflow:auto;overscroll-behavior:contain;background:#12151f;border:1px solid var(--slm-line);
|
|
7003
7009
|
border-radius:14px;padding:20px;box-shadow:0 24px 70px rgba(0,0,0,.6);
|
|
7004
7010
|
animation:slm-ch-rise var(--slm-mo-base) var(--slm-mo-out)}
|
|
7005
7011
|
.slm-ch-dialog h3{margin:0 0 4px;font-size:16px;font-weight:800;letter-spacing:-.01em}
|
|
@@ -7026,7 +7032,7 @@ var CHANNELS_CSS = (
|
|
|
7026
7032
|
overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
|
7027
7033
|
.slm-ch-err{color:#f1a4a6;font-size:11.5px;margin-top:6px}
|
|
7028
7034
|
|
|
7029
|
-
/*
|
|
7035
|
+
/* buyer links \u2014 STATUS only; there is no Copy control on this card */
|
|
7030
7036
|
.slm-ch-link{padding:10px 11px;border:1px solid var(--slm-line);border-radius:10px;background:var(--slm-surface);
|
|
7031
7037
|
margin-bottom:8px}
|
|
7032
7038
|
.slm-ch-link .lk-head{display:flex;align-items:center;gap:8px}
|
|
@@ -7045,8 +7051,8 @@ var CHANNELS_CSS = (
|
|
|
7045
7051
|
.slm-ch-radio input{flex:none;margin-top:2px}
|
|
7046
7052
|
.slm-ch-radio b{display:block;font-weight:800;margin-bottom:2px}
|
|
7047
7053
|
.slm-ch-radio .why{display:block;color:var(--slm-muted);font-size:11.5px;line-height:1.45}
|
|
7048
|
-
.slm-ch-seatlist{max-height:44vh;overflow:auto;border:1px solid var(--slm-line);
|
|
7049
|
-
background:var(--slm-surface);margin-top:10px}
|
|
7054
|
+
.slm-ch-seatlist{max-height:44vh;overflow:auto;overscroll-behavior:contain;border:1px solid var(--slm-line);
|
|
7055
|
+
border-radius:10px;background:var(--slm-surface);margin-top:10px}
|
|
7050
7056
|
.slm-ch-seatgroup{padding:8px 10px;border-bottom:1px solid var(--slm-line);display:flex;align-items:center;
|
|
7051
7057
|
justify-content:space-between;gap:8px;font-size:11px;font-weight:800;color:var(--slm-muted);position:sticky;top:0;
|
|
7052
7058
|
background:var(--slm-surface)}
|
|
@@ -7096,12 +7102,12 @@ function bucketRowsHtml(rows) {
|
|
|
7096
7102
|
</div>`).join("");
|
|
7097
7103
|
}
|
|
7098
7104
|
var SERVER_INTEGRATION_HTML = `
|
|
7099
|
-
<p class="slm-eyebrow" style="margin-top:18px">
|
|
7100
|
-
<p class="slm-hint">There is nothing to set up on this screen. Your own server mints a short-lived buyer
|
|
7105
|
+
<p class="slm-eyebrow" style="margin-top:18px">On your website</p>
|
|
7106
|
+
<p class="slm-hint">There is nothing more to set up on this screen. Your own server mints a short-lived buyer
|
|
7101
7107
|
access session for this channel with the SeatLayer server SDK and hands it to the widget. A channel name
|
|
7102
7108
|
on its own never grants access.</p>
|
|
7103
7109
|
<p class="slm-note"><a class="slm-linkbtn" href="https://docs.seatlayer.io/server-api/channels"
|
|
7104
|
-
target="_blank" rel="noreferrer noopener">Read the
|
|
7110
|
+
target="_blank" rel="noreferrer noopener">Read the website integration guide \u2192</a></p>`;
|
|
7105
7111
|
function esc(value) {
|
|
7106
7112
|
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
7107
7113
|
}
|
|
@@ -7182,6 +7188,17 @@ var ChannelsMode = class {
|
|
|
7182
7188
|
this.stagedDoneTimer = null;
|
|
7183
7189
|
this.lastSelectionCount = 0;
|
|
7184
7190
|
this.lastCounts = /* @__PURE__ */ new Map();
|
|
7191
|
+
/** The markup currently in the rail. An identical repaint is skipped, which is
|
|
7192
|
+
* what keeps the organizer's scroll position (and open <select>) alive. */
|
|
7193
|
+
this.railHtml = null;
|
|
7194
|
+
/**
|
|
7195
|
+
* The `assignmentVersion` the allocation map was built from. Walking every
|
|
7196
|
+
* allocation page is the expensive half of a refresh and the server already
|
|
7197
|
+
* tells us, in the channels response, whether ANY seat moved. Unchanged
|
|
7198
|
+
* version, unchanged allocation — so the walk is skipped entirely.
|
|
7199
|
+
*/
|
|
7200
|
+
this.allocationVersion = null;
|
|
7201
|
+
this.onVisibility = null;
|
|
7185
7202
|
this.host = host;
|
|
7186
7203
|
this.caps = capabilities;
|
|
7187
7204
|
}
|
|
@@ -7192,6 +7209,7 @@ var ChannelsMode = class {
|
|
|
7192
7209
|
this.active = true;
|
|
7193
7210
|
this.mapIntent = "pan";
|
|
7194
7211
|
this.focusedSectionId = null;
|
|
7212
|
+
this.railHtml = null;
|
|
7195
7213
|
this.ensureLayer();
|
|
7196
7214
|
this.host.root.classList.add("ch-mode");
|
|
7197
7215
|
this.applySheetClasses();
|
|
@@ -7200,8 +7218,15 @@ var ChannelsMode = class {
|
|
|
7200
7218
|
this.onInteractionChange?.();
|
|
7201
7219
|
void this.refresh();
|
|
7202
7220
|
this.pollTimer = setInterval(() => {
|
|
7221
|
+
if (typeof document !== "undefined" && document.hidden) return;
|
|
7203
7222
|
void this.refresh({ quiet: true });
|
|
7204
7223
|
}, POLL_MS);
|
|
7224
|
+
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
7225
|
+
this.onVisibility = () => {
|
|
7226
|
+
if (this.active && !document.hidden) void this.refresh({ quiet: true });
|
|
7227
|
+
};
|
|
7228
|
+
document.addEventListener("visibilitychange", this.onVisibility);
|
|
7229
|
+
}
|
|
7205
7230
|
}
|
|
7206
7231
|
/** Called when the cockpit leaves Channels mode. Everything this mode painted
|
|
7207
7232
|
* over the map goes with it — no other tool ever inherits a channel overlay. */
|
|
@@ -7210,6 +7235,11 @@ var ChannelsMode = class {
|
|
|
7210
7235
|
this.active = false;
|
|
7211
7236
|
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
7212
7237
|
this.pollTimer = null;
|
|
7238
|
+
if (this.onVisibility && typeof document !== "undefined") {
|
|
7239
|
+
document.removeEventListener("visibilitychange", this.onVisibility);
|
|
7240
|
+
}
|
|
7241
|
+
this.onVisibility = null;
|
|
7242
|
+
this.railHtml = null;
|
|
7213
7243
|
this.closeDialog({ restoreFocus: false });
|
|
7214
7244
|
this.links = [];
|
|
7215
7245
|
this.linksChannelId = null;
|
|
@@ -7298,8 +7328,11 @@ var ChannelsMode = class {
|
|
|
7298
7328
|
if (!this.targetChannelId) {
|
|
7299
7329
|
this.targetChannelId = list.channels.find((c) => c.state === "active")?.id ?? PUBLIC_CHANNEL_ID;
|
|
7300
7330
|
}
|
|
7301
|
-
|
|
7302
|
-
|
|
7331
|
+
if (this.allocationVersion !== list.assignmentVersion) {
|
|
7332
|
+
await this.loadAllocation(seq);
|
|
7333
|
+
if (superseded()) return;
|
|
7334
|
+
this.allocationVersion = list.assignmentVersion;
|
|
7335
|
+
}
|
|
7303
7336
|
if (this.detailChannelId) await this.loadLinks(this.detailChannelId);
|
|
7304
7337
|
if (superseded()) return;
|
|
7305
7338
|
this.loading = false;
|
|
@@ -7718,35 +7751,59 @@ var ChannelsMode = class {
|
|
|
7718
7751
|
this.paintOverlay();
|
|
7719
7752
|
}
|
|
7720
7753
|
}
|
|
7754
|
+
/**
|
|
7755
|
+
* Replace the rail's markup — but only when it actually differs, and never at
|
|
7756
|
+
* the cost of where the organizer had scrolled to.
|
|
7757
|
+
*
|
|
7758
|
+
* The rail repaints on a clock, on every selection change and after every
|
|
7759
|
+
* mutation. Rewriting `innerHTML` each time resets `scrollTop`, which is
|
|
7760
|
+
* exactly what "the rail gets stuck" was: scroll down to Create channel, the
|
|
7761
|
+
* poll ticks, and the list snaps back to the top under the cursor. So skip the
|
|
7762
|
+
* write when the markup is byte-identical, and restore the offset when it is
|
|
7763
|
+
* not.
|
|
7764
|
+
*
|
|
7765
|
+
* Returns whether the DOM was rewritten. Callers must only re-wire listeners
|
|
7766
|
+
* when it was — a skipped paint keeps the old nodes AND their listeners, so
|
|
7767
|
+
* re-wiring would double every handler.
|
|
7768
|
+
*/
|
|
7769
|
+
setRailHtml(html) {
|
|
7770
|
+
if (html === this.railHtml) return false;
|
|
7771
|
+
const rail = this.host.rail;
|
|
7772
|
+
const scrollTop = rail.scrollTop;
|
|
7773
|
+
rail.innerHTML = html;
|
|
7774
|
+
this.railHtml = html;
|
|
7775
|
+
if (scrollTop) rail.scrollTop = scrollTop;
|
|
7776
|
+
return true;
|
|
7777
|
+
}
|
|
7721
7778
|
paintRail() {
|
|
7722
7779
|
if (!this.active) return;
|
|
7723
7780
|
const rail = this.host.rail;
|
|
7724
7781
|
if (!this.caps.view) {
|
|
7725
|
-
|
|
7726
|
-
<p class="slm-hint">You need channel-management permission on this event to see allocations.</p
|
|
7782
|
+
this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
|
|
7783
|
+
<p class="slm-hint">You need channel-management permission on this event to see allocations.</p>`);
|
|
7727
7784
|
return;
|
|
7728
7785
|
}
|
|
7729
7786
|
if (this.loading && !this.list) {
|
|
7730
|
-
|
|
7731
|
-
<div class="slm-empty">Loading allocations\u2026</div
|
|
7787
|
+
this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
|
|
7788
|
+
<div class="slm-empty">Loading allocations\u2026</div>`);
|
|
7732
7789
|
return;
|
|
7733
7790
|
}
|
|
7734
7791
|
if (!this.list && this.loadError) {
|
|
7735
|
-
|
|
7792
|
+
if (this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
|
|
7736
7793
|
<div class="slm-ch-alert err" role="alert"><span>\u26A0</span>
|
|
7737
7794
|
<span><b>Couldn't load sales channels.</b> Everything else on this event still works.
|
|
7738
|
-
<button type="button" data-ch-act="retry">Try again</button></span></div
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7795
|
+
<button type="button" data-ch-act="retry">Try again</button></span></div>`)) {
|
|
7796
|
+
rail.querySelector('[data-ch-act="retry"]')?.addEventListener("click", () => {
|
|
7797
|
+
void this.refresh();
|
|
7798
|
+
});
|
|
7799
|
+
}
|
|
7742
7800
|
return;
|
|
7743
7801
|
}
|
|
7744
7802
|
const selection = this.host.selectionLabels();
|
|
7745
7803
|
const grab = this.host.isCompact() ? `<div class="slm-ch-grab"><span class="slm-ch-grabbar"></span></div>` : "";
|
|
7746
7804
|
const segment = this.viewSegmentHtml();
|
|
7747
7805
|
const body = this.view === "preview" ? this.previewRailHtml() : this.detailChannelId ? this.detailRailHtml(this.detailChannelId) : selection.length && this.caps.manage ? this.selectionRailHtml(selection) : this.listRailHtml();
|
|
7748
|
-
|
|
7749
|
-
this.wireRail();
|
|
7806
|
+
if (this.setRailHtml(`${grab}${segment}${body}`)) this.wireRail();
|
|
7750
7807
|
this.paintStagedBar();
|
|
7751
7808
|
}
|
|
7752
7809
|
viewSegmentHtml() {
|
|
@@ -7877,22 +7934,7 @@ var ChannelsMode = class {
|
|
|
7877
7934
|
<button type="button" class="slm-btn ghost" data-ch-act="archive">Archive\u2026</button>
|
|
7878
7935
|
</div>
|
|
7879
7936
|
<p class="slm-note">Archive returns the allocation to a destination you choose. Nothing is ever deleted silently.</p>` : "";
|
|
7880
|
-
const
|
|
7881
|
-
const intents = ["none", "internal", "server", "hosted_link"];
|
|
7882
|
-
const selfServiceGap = (intent === "server" || intent === "hosted_link") && !channel.access?.hasActiveGrants;
|
|
7883
|
-
const access = this.caps.manage ? `
|
|
7884
|
-
<p class="slm-eyebrow" style="margin-top:14px">Buyer access</p>
|
|
7885
|
-
<div class="slm-field">
|
|
7886
|
-
<label for="slm-ch-intent">How should buyers reach this channel?</label>
|
|
7887
|
-
<select class="slm-select" id="slm-ch-intent" data-ch-intent>
|
|
7888
|
-
${intents.map((value) => `<option value="${value}"${value === intent ? " selected" : ""}>${esc(accessIntentLabel(value))}</option>`).join("")}
|
|
7889
|
-
</select>
|
|
7890
|
-
</div>
|
|
7891
|
-
<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>
|
|
7892
|
-
${selfServiceGap ? `<div class="slm-ch-alert warn"><span>\u26A0</span>
|
|
7893
|
-
<span>This channel is marked for buyer self-service but no buyer has been let in yet.</span></div>` : ""}
|
|
7894
|
-
${this.hostedLinksHtml()}
|
|
7895
|
-
${SERVER_INTEGRATION_HTML}` : "";
|
|
7937
|
+
const access = this.caps.manage ? this.distributeHtml(channel) : "";
|
|
7896
7938
|
return `
|
|
7897
7939
|
<p class="slm-eyebrow">
|
|
7898
7940
|
<button type="button" class="slm-linkbtn" data-ch-act="back" style="text-align:left">\u2039 All channels</button>
|
|
@@ -7902,7 +7944,54 @@ var ChannelsMode = class {
|
|
|
7902
7944
|
${access}
|
|
7903
7945
|
${lifecycle}`;
|
|
7904
7946
|
}
|
|
7905
|
-
|
|
7947
|
+
/**
|
|
7948
|
+
* "Distribute" — how the seats in this channel actually reach a buyer.
|
|
7949
|
+
*
|
|
7950
|
+
* This replaced a four-value "access intent" picker (none / internal /
|
|
7951
|
+
* hosted_link / server). Two of those values did nothing anywhere: no buyer or
|
|
7952
|
+
* inventory path reads `access_intent`, so `none` and `internal` were labels an
|
|
7953
|
+
* organizer could set and then wait forever for something to happen. A third,
|
|
7954
|
+
* `hosted_link`, is not the organizer's to choose at all — the server sets it
|
|
7955
|
+
* when a buyer link is created and clears it when the last live one is revoked.
|
|
7956
|
+
*
|
|
7957
|
+
* So this is two ACTIONS, not a setting: create a buyer link, or point the
|
|
7958
|
+
* channel at a website integration. Both of them do something the moment they
|
|
7959
|
+
* are pressed. A legacy row still carrying `none` or `internal` renders the
|
|
7960
|
+
* neutral "not distributed yet" state with both actions offered — the stored
|
|
7961
|
+
* value is left alone (it is organizer-declared metadata and the API that
|
|
7962
|
+
* writes it is unchanged), it simply no longer has a control of its own.
|
|
7963
|
+
*/
|
|
7964
|
+
distributeHtml(channel) {
|
|
7965
|
+
const intent = channel.access?.intent ?? "none";
|
|
7966
|
+
const live = channel.access?.hasActiveGrants === true;
|
|
7967
|
+
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.";
|
|
7968
|
+
const option = (title, why, action, cta, primary) => `<div class="slm-ch-dist">
|
|
7969
|
+
<b>${esc(title)}</b>
|
|
7970
|
+
<span class="why">${esc(why)}</span>
|
|
7971
|
+
<button type="button" class="slm-btn${primary ? "" : " ghost"}" data-ch-act="${esc(action)}">${esc(cta)}</button>
|
|
7972
|
+
</div>`;
|
|
7973
|
+
const linksSupported = this.linksState !== "unsupported";
|
|
7974
|
+
return `
|
|
7975
|
+
<p class="slm-eyebrow" style="margin-top:14px">Distribute</p>
|
|
7976
|
+
<p class="slm-hint">${esc(state)}</p>
|
|
7977
|
+
${linksSupported ? option(
|
|
7978
|
+
"Sell with a buyer link",
|
|
7979
|
+
"SeatLayer makes a link you send to a named group. They open it and buy only these seats.",
|
|
7980
|
+
"link-create",
|
|
7981
|
+
this.links.some(accessLinkIsLive) ? "Create another buyer link" : "Create buyer link",
|
|
7982
|
+
true
|
|
7983
|
+
) : ""}
|
|
7984
|
+
${option(
|
|
7985
|
+
"Integrate a website or app",
|
|
7986
|
+
"Your website's backend grants each buyer access \u2014 set up on the Embed page.",
|
|
7987
|
+
"embed-code",
|
|
7988
|
+
"Get embed code",
|
|
7989
|
+
false
|
|
7990
|
+
)}
|
|
7991
|
+
${this.hostedLinksHtml()}
|
|
7992
|
+
${intent === "server" ? SERVER_INTEGRATION_HTML : ""}`;
|
|
7993
|
+
}
|
|
7994
|
+
// ---- buyer links ----------------------------------------------------------
|
|
7906
7995
|
/**
|
|
7907
7996
|
* Read the status projection for the open channel. Never paints — the caller
|
|
7908
7997
|
* decides when the rail repaints, so a poll-driven reload does not fight a
|
|
@@ -7932,32 +8021,34 @@ var ChannelsMode = class {
|
|
|
7932
8021
|
}
|
|
7933
8022
|
}
|
|
7934
8023
|
/**
|
|
7935
|
-
* The
|
|
8024
|
+
* The buyer-link status section of the detail panel.
|
|
7936
8025
|
*
|
|
7937
8026
|
* STATUS ONLY, by design (comp 06 `hosted`): label, state, expiry,
|
|
7938
8027
|
* redemptions, seats per buyer, live sessions. There is no Copy control here
|
|
7939
8028
|
* and no field to hang one on — the URL was shown once at creation and cannot
|
|
7940
8029
|
* be produced again. Rotation is the recovery path, and it says so.
|
|
8030
|
+
*
|
|
8031
|
+
* Creating a link is the Distribute card's action, not this section's, so a
|
|
8032
|
+
* channel with no links renders nothing here rather than an empty heading and
|
|
8033
|
+
* a second button saying the same thing.
|
|
7941
8034
|
*/
|
|
7942
8035
|
hostedLinksHtml() {
|
|
7943
|
-
const eyebrow = `<p class="slm-eyebrow" style="margin-top:18px">
|
|
8036
|
+
const eyebrow = `<p class="slm-eyebrow" style="margin-top:18px">Buyer links</p>`;
|
|
7944
8037
|
if (this.linksState === "unsupported") {
|
|
7945
8038
|
return `${eyebrow}<div class="slm-ch-alert warn"><span>\u2139</span>
|
|
7946
|
-
<span><b>
|
|
8039
|
+
<span><b>Buyer links need a newer server.</b> Everything else on this channel works normally.</span></div>`;
|
|
7947
8040
|
}
|
|
7948
8041
|
if (this.linksState === "error") {
|
|
7949
8042
|
return `${eyebrow}<div class="slm-ch-alert err" role="alert"><span>\u26A0</span>
|
|
7950
8043
|
<span><b>Couldn't load this channel's links.</b>
|
|
7951
8044
|
<button type="button" data-ch-act="link-reload">Try again</button></span></div>`;
|
|
7952
8045
|
}
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
${live ? "Create another hosted link" : "Create hosted access link"}</button>` : "";
|
|
8046
|
+
if (this.linksState === "loading" && !this.links.length) {
|
|
8047
|
+
return `${eyebrow}<div class="slm-empty">Loading links\u2026</div>`;
|
|
8048
|
+
}
|
|
8049
|
+
if (!this.links.length) return "";
|
|
7958
8050
|
return `${eyebrow}
|
|
7959
|
-
${
|
|
7960
|
-
${create}
|
|
8051
|
+
${this.links.map((link) => this.linkCardHtml(link)).join("")}
|
|
7961
8052
|
<p class="slm-note">A link is shown once, when you create it. SeatLayer keeps only a fingerprint of it, so it
|
|
7962
8053
|
can never be shown again \u2014 if a link is lost, rotate it and send the fresh one.</p>`;
|
|
7963
8054
|
}
|
|
@@ -7976,7 +8067,7 @@ var ChannelsMode = class {
|
|
|
7976
8067
|
</div>` : "";
|
|
7977
8068
|
return `<div class="slm-ch-link">
|
|
7978
8069
|
<span class="lk-head">
|
|
7979
|
-
<span class="lk-name">${esc(link.label || "
|
|
8070
|
+
<span class="lk-name">${esc(link.label || "Buyer link")}</span>
|
|
7980
8071
|
<span class="slm-ch-badge ${badge.kind}">${esc(badge.text)}</span>
|
|
7981
8072
|
</span>
|
|
7982
8073
|
<div class="slm-ch-meter" role="img"
|
|
@@ -8079,10 +8170,6 @@ var ChannelsMode = class {
|
|
|
8079
8170
|
this.previewIncludePublic = includePublic.checked;
|
|
8080
8171
|
void this.loadPreview();
|
|
8081
8172
|
});
|
|
8082
|
-
const intent = rail.querySelector("[data-ch-intent]");
|
|
8083
|
-
intent?.addEventListener("change", () => {
|
|
8084
|
-
void this.setAccessIntent(intent.value);
|
|
8085
|
-
});
|
|
8086
8173
|
const grab = rail.querySelector(".slm-ch-grab");
|
|
8087
8174
|
grab?.addEventListener("click", () => this.cycleDetent());
|
|
8088
8175
|
rail.querySelectorAll("[data-ch-act]").forEach((button) => {
|
|
@@ -8127,6 +8214,13 @@ var ChannelsMode = class {
|
|
|
8127
8214
|
case "link-create":
|
|
8128
8215
|
this.openDialog({ kind: "linkCreate", channelId: this.detailChannelId });
|
|
8129
8216
|
break;
|
|
8217
|
+
// The one thing this screen can actually do for a website integration is
|
|
8218
|
+
// record that the channel is meant for one — which is exactly the flag the
|
|
8219
|
+
// dashboard's Embed page reads before it offers the snippet. Saying so is
|
|
8220
|
+
// honest; a fake "copy code" button on a screen with no code would not be.
|
|
8221
|
+
case "embed-code":
|
|
8222
|
+
void this.chooseWebsiteIntegration();
|
|
8223
|
+
break;
|
|
8130
8224
|
case "link-reload":
|
|
8131
8225
|
if (this.detailChannelId) void this.reloadLinks();
|
|
8132
8226
|
break;
|
|
@@ -8467,12 +8561,27 @@ var ChannelsMode = class {
|
|
|
8467
8561
|
});
|
|
8468
8562
|
});
|
|
8469
8563
|
}
|
|
8564
|
+
/**
|
|
8565
|
+
* "Get embed code" — mark the channel as reached through the organizer's own
|
|
8566
|
+
* backend. The write itself is `setChannelAccessIntent(…, 'server')`, the same
|
|
8567
|
+
* API the old picker called; the difference is that it is now a deliberate
|
|
8568
|
+
* action with a consequence the organizer is told about, rather than one of
|
|
8569
|
+
* four dropdown values with no observable effect.
|
|
8570
|
+
*/
|
|
8571
|
+
async chooseWebsiteIntegration() {
|
|
8572
|
+
const channelId = this.detailChannelId;
|
|
8573
|
+
if (!channelId) return;
|
|
8574
|
+
await this.setAccessIntent("server");
|
|
8575
|
+
}
|
|
8470
8576
|
async setAccessIntent(accessIntent) {
|
|
8471
8577
|
const channelId = this.detailChannelId;
|
|
8472
8578
|
if (!channelId || !this.caps.manage) return;
|
|
8473
8579
|
try {
|
|
8474
8580
|
await this.host.api.setChannelAccessIntent(this.host.eventKey, channelId, accessIntent);
|
|
8475
8581
|
await this.refresh();
|
|
8582
|
+
if (accessIntent === "server") {
|
|
8583
|
+
this.host.toast("Marked for your website. The embed code is on the Embed page.", "ok");
|
|
8584
|
+
}
|
|
8476
8585
|
} catch (err) {
|
|
8477
8586
|
this.host.toast("Couldn't save how buyers reach this channel.", "err");
|
|
8478
8587
|
this.host.onError(err);
|
|
@@ -8613,7 +8722,7 @@ var ChannelsMode = class {
|
|
|
8613
8722
|
});
|
|
8614
8723
|
});
|
|
8615
8724
|
}
|
|
8616
|
-
// ----
|
|
8725
|
+
// ---- buyer-link dialogs ---------------------------------------------------
|
|
8617
8726
|
async reloadLinks() {
|
|
8618
8727
|
const channelId = this.detailChannelId;
|
|
8619
8728
|
if (!channelId) return;
|
|
@@ -8656,7 +8765,7 @@ var ChannelsMode = class {
|
|
|
8656
8765
|
return;
|
|
8657
8766
|
}
|
|
8658
8767
|
this.renderScrim(`
|
|
8659
|
-
<h3 id="slm-ch-dlg-title">Create a
|
|
8768
|
+
<h3 id="slm-ch-dlg-title">Create a buyer link for ${esc(channel.name)}</h3>
|
|
8660
8769
|
<p class="sub">Anyone who opens the link can buy from this channel's allocation \u2014 and only from it.
|
|
8661
8770
|
You'll see the link once, right after you create it.</p>
|
|
8662
8771
|
<div class="slm-field">
|
|
@@ -8781,7 +8890,7 @@ var ChannelsMode = class {
|
|
|
8781
8890
|
copy?.addEventListener("click", () => {
|
|
8782
8891
|
const ok = () => {
|
|
8783
8892
|
copy.textContent = "Copied";
|
|
8784
|
-
this.announce("
|
|
8893
|
+
this.announce("Buyer link copied.");
|
|
8785
8894
|
};
|
|
8786
8895
|
const clipboard = typeof navigator === "undefined" ? null : navigator.clipboard;
|
|
8787
8896
|
if (clipboard?.writeText) {
|
|
@@ -8791,7 +8900,7 @@ var ChannelsMode = class {
|
|
|
8791
8900
|
selectSecret(dialog);
|
|
8792
8901
|
});
|
|
8793
8902
|
});
|
|
8794
|
-
this.announce("Your
|
|
8903
|
+
this.announce("Your buyer link is ready and is shown once.");
|
|
8795
8904
|
}
|
|
8796
8905
|
/**
|
|
8797
8906
|
* Rotate. The organizer must SAY what happens to the buyers already inside —
|
|
@@ -8810,7 +8919,7 @@ var ChannelsMode = class {
|
|
|
8810
8919
|
<span><b>${sessions.toLocaleString()} buyer${sessions === 1 ? "" : "s"}</b> got in with the current link
|
|
8811
8920
|
and still ${sessions === 1 ? "has" : "have"} active access.</span></div>` : "";
|
|
8812
8921
|
this.renderScrim(`
|
|
8813
|
-
<h3 id="slm-ch-dlg-title">Rotate the ${esc(link.label || "
|
|
8922
|
+
<h3 id="slm-ch-dlg-title">Rotate the ${esc(link.label || "buyer")} link?</h3>
|
|
8814
8923
|
<p class="sub">The current link stops opening immediately and cannot be restored. You will get a new
|
|
8815
8924
|
link to copy \u2014 shown once.</p>
|
|
8816
8925
|
${warning}
|
|
@@ -8869,7 +8978,7 @@ var ChannelsMode = class {
|
|
|
8869
8978
|
}
|
|
8870
8979
|
const sessions = link.activeSessions ?? 0;
|
|
8871
8980
|
this.renderScrim(`
|
|
8872
|
-
<h3 id="slm-ch-dlg-title">Revoke the ${esc(link.label || "
|
|
8981
|
+
<h3 id="slm-ch-dlg-title">Revoke the ${esc(link.label || "buyer")} link?</h3>
|
|
8873
8982
|
<p class="sub">It stops opening immediately and cannot be restored \u2014 there is no undo, and no way to
|
|
8874
8983
|
bring the same URL back. Seats already bought through it keep their sale.</p>
|
|
8875
8984
|
${sessions ? `<label class="slm-ch-radio">
|
|
@@ -8986,7 +9095,12 @@ var LEGEND = [
|
|
|
8986
9095
|
var MANAGER_CSS = (
|
|
8987
9096
|
/* @sl-css */
|
|
8988
9097
|
`
|
|
8989
|
-
|
|
9098
|
+
/* The floor was 480px, which trapped the cockpit in any host shorter than that:
|
|
9099
|
+
every ancestor here is overflow:hidden, so the bottom of the rail (Create
|
|
9100
|
+
channel, Show archived) was rendered but clipped away by the host and could
|
|
9101
|
+
not be reached by scrolling anything. 320px still keeps an unsized host from
|
|
9102
|
+
collapsing to the top bar, and the rail scrolls to its full content below. */
|
|
9103
|
+
.slm{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:320px;overflow:hidden;
|
|
8990
9104
|
background:var(--slm-bg);color:var(--slm-text);font-family:var(--slm-font);border-radius:var(--slm-radius);
|
|
8991
9105
|
/* Motion tokens (motion-system \xA72), declared by the cockpit ROOT rather than
|
|
8992
9106
|
borrowed from CHANNELS_CSS. The base cockpit animates whether or not
|
|
@@ -9054,7 +9168,7 @@ var MANAGER_CSS = (
|
|
|
9054
9168
|
.slm-liveeventdot{width:8px;height:8px;border-radius:50%;flex:none}.slm-liveeventcopy{min-width:0;overflow:hidden;text-overflow:ellipsis;
|
|
9055
9169
|
white-space:nowrap;font-size:12px;font-weight:800}.slm-liveeventhint{color:var(--slm-muted);font-size:10px;white-space:nowrap}
|
|
9056
9170
|
.slm-rail{width:320px;flex:none;border-left:1px solid var(--slm-line);display:flex;flex-direction:column;min-height:0}
|
|
9057
|
-
.slm-railscroll{flex:1;overflow-y:auto;padding:16px}
|
|
9171
|
+
.slm-railscroll{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px}
|
|
9058
9172
|
.slm-eyebrow{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--slm-muted);font-weight:800;margin-bottom:6px}
|
|
9059
9173
|
.slm-hint{font-size:12.5px;color:var(--slm-muted);line-height:1.5;margin-bottom:14px}
|
|
9060
9174
|
|
|
@@ -9119,7 +9233,8 @@ var MANAGER_CSS = (
|
|
|
9119
9233
|
color:var(--slm-muted);font-size:10.5px}
|
|
9120
9234
|
.slm-linkbtn{font-size:11px!important;font-weight:800!important;color:var(--slm-accent)!important;text-align:right}
|
|
9121
9235
|
.slm-linkbtn:disabled{opacity:.45;cursor:not-allowed}
|
|
9122
|
-
.slm-blockedlist{max-height:246px;overflow:auto;border:1px solid var(--slm-line);
|
|
9236
|
+
.slm-blockedlist{max-height:246px;overflow:auto;overscroll-behavior:contain;border:1px solid var(--slm-line);
|
|
9237
|
+
border-radius:10px;background:var(--slm-surface)}
|
|
9123
9238
|
.slm-blockeditem{display:grid!important;grid-template-columns:18px minmax(0,1fr);width:100%;gap:8px;padding:8px 9px!important;
|
|
9124
9239
|
border-bottom:1px solid var(--slm-line)!important;text-align:left!important}
|
|
9125
9240
|
.slm-blockeditem:last-child{border-bottom:0!important}.slm-blockeditem:hover{background:rgba(255,255,255,.035)!important}
|