@relaya-chat/react 1.0.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.
@@ -0,0 +1,3159 @@
1
+ import { useCallback as e, useEffect as t, useRef as n, useState as r } from "react";
2
+ import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
3
+ //#region ../core/dist/types.js
4
+ var s = {
5
+ READ: "chat.read",
6
+ POST: "chat.post",
7
+ EDIT_OWN: "chat.edit_own",
8
+ DELETE_OWN: "chat.delete_own",
9
+ DELETE_ANY: "chat.delete_any",
10
+ REPORT: "chat.report",
11
+ BAN_USER: "chat.ban_user",
12
+ MANAGE_ROLES: "chat.manage_roles",
13
+ MENTION_CHANNEL: "chat.mention_channel"
14
+ }, c = class {
15
+ baseUrl;
16
+ getToken;
17
+ constructor(e, t) {
18
+ this.baseUrl = e, this.getToken = t;
19
+ }
20
+ authHeaders() {
21
+ let e = this.getToken();
22
+ return e ? { Authorization: `Bearer ${e}` } : {};
23
+ }
24
+ async request(e, t, n) {
25
+ let r = `${this.baseUrl}${t}`, i = { ...this.authHeaders() };
26
+ n !== void 0 && (i["Content-Type"] = "application/json");
27
+ let a = await fetch(r, {
28
+ method: e,
29
+ headers: i,
30
+ body: n === void 0 ? void 0 : JSON.stringify(n)
31
+ });
32
+ if (a.status !== 204) {
33
+ if (!a.ok) {
34
+ let e = {};
35
+ try {
36
+ e = await a.json();
37
+ } catch {}
38
+ let t = e?.error;
39
+ throw {
40
+ status: a.status,
41
+ code: (typeof t == "object" ? t?.code : void 0) ?? "API_ERROR",
42
+ message: (typeof t == "object" ? t?.message : t) ?? a.statusText
43
+ };
44
+ }
45
+ return a.json();
46
+ }
47
+ }
48
+ async parseResponse(e) {
49
+ if (e.status !== 204) {
50
+ if (!e.ok) {
51
+ let t = {};
52
+ try {
53
+ t = await e.json();
54
+ } catch {}
55
+ let n = t?.error;
56
+ throw {
57
+ status: e.status,
58
+ code: (typeof n == "object" ? n?.code : void 0) ?? "API_ERROR",
59
+ message: (typeof n == "object" ? n?.message : n) ?? e.statusText
60
+ };
61
+ }
62
+ return e.json();
63
+ }
64
+ }
65
+ async login(e, t) {
66
+ return this.request("POST", "/auth/login", {
67
+ email: e,
68
+ stationSlug: t
69
+ });
70
+ }
71
+ async requestCode(e, t) {
72
+ return this.request("POST", "/auth/request-code", {
73
+ email: e,
74
+ stationSlug: t
75
+ });
76
+ }
77
+ async verifyCode(e, t, n) {
78
+ return this.request("POST", "/auth/verify-code", {
79
+ pendingId: e,
80
+ code: t,
81
+ stationSlug: n
82
+ });
83
+ }
84
+ async verify(e, t) {
85
+ return this.request("GET", `/auth/verify?token=${encodeURIComponent(e)}&station=${encodeURIComponent(t)}`);
86
+ }
87
+ async refresh(e) {
88
+ return this.request("POST", "/auth/refresh", { refreshToken: e });
89
+ }
90
+ async getStation(e) {
91
+ return this.request("GET", `/api/chat/stations/${encodeURIComponent(e)}`);
92
+ }
93
+ async getMessages(e, t) {
94
+ let n = new URLSearchParams();
95
+ t?.before && n.set("before", t.before), t?.after && n.set("after", t.after), t?.limit !== void 0 && n.set("limit", String(t.limit));
96
+ let r = n.toString() ? `?${n.toString()}` : "";
97
+ return this.request("GET", `/api/chat/${e}/messages${r}`);
98
+ }
99
+ async deleteMessage(e, t) {
100
+ return this.request("DELETE", `/api/chat/${e}/messages/${t}`);
101
+ }
102
+ async editMessage(e, t, n) {
103
+ return this.request("PATCH", `/api/chat/${e}/messages/${t}`, { content: n });
104
+ }
105
+ async createReport(e, t, n, r) {
106
+ return this.request("POST", `/api/chat/${e}/messages/${t}/report`, {
107
+ reason: n,
108
+ details: r
109
+ });
110
+ }
111
+ async getReports(e, t) {
112
+ let n = new URLSearchParams();
113
+ t?.status && n.set("status", t.status), t?.limit !== void 0 && n.set("limit", String(t.limit)), t?.offset !== void 0 && n.set("offset", String(t.offset));
114
+ let r = n.toString() ? `?${n.toString()}` : "";
115
+ return this.request("GET", `/api/chat/${e}/reports${r}`);
116
+ }
117
+ async updateReport(e, t, n) {
118
+ return this.request("PATCH", `/api/chat/${e}/reports/${t}`, n);
119
+ }
120
+ async createBan(e, t, n) {
121
+ return this.request("POST", `/api/chat/${e}/bans`, {
122
+ userId: t,
123
+ ...n
124
+ });
125
+ }
126
+ async liftBan(e, t) {
127
+ return this.request("DELETE", `/api/chat/${e}/bans/${t}`);
128
+ }
129
+ async getBans(e, t = !0) {
130
+ let n = t ? "?active=true" : "";
131
+ return this.request("GET", `/api/chat/${e}/bans${n}`);
132
+ }
133
+ async getMembers(e) {
134
+ return this.request("GET", `/api/chat/${e}/members`);
135
+ }
136
+ async getMembersAdmin(e) {
137
+ return this.request("GET", `/api/chat/${e}/members/admin`);
138
+ }
139
+ async updateMemberRole(e, t, n) {
140
+ return this.request("PATCH", `/api/chat/${e}/members/${t}/roles`, { roleId: n });
141
+ }
142
+ async patchMemberRoles(e, t, n) {
143
+ return this.request("PATCH", `/api/chat/${e}/members/${t}/roles`, n);
144
+ }
145
+ async getMe(e) {
146
+ return this.request("GET", `/api/chat/${e}/me`);
147
+ }
148
+ async updateChatName(e, t) {
149
+ return this.request("PATCH", `/api/chat/${e}/me`, { chatName: t });
150
+ }
151
+ async getModerationConfig(e) {
152
+ return this.request("GET", `/api/chat/${e}/moderation/config`);
153
+ }
154
+ async updateModerationConfig(e, t) {
155
+ return this.request("PATCH", `/api/chat/${e}/moderation/config`, t);
156
+ }
157
+ async getPresenceConfig(e) {
158
+ return this.request("GET", `/api/chat/${e}/presence/config`);
159
+ }
160
+ async updatePresenceConfig(e, t) {
161
+ return this.request("PATCH", `/api/chat/${e}/presence/config`, t);
162
+ }
163
+ async getStickers(e) {
164
+ let t = await this.request("GET", `/api/chat/${e}/stickers`);
165
+ return this.baseUrl && (t.stickers = t.stickers.map((e) => e.url.startsWith("/") ? {
166
+ ...e,
167
+ url: `${this.baseUrl}${e.url}`
168
+ } : e)), t;
169
+ }
170
+ async uploadSticker(e, t, n) {
171
+ let r = await fetch(`${this.baseUrl}/api/chat/${e}/stickers/upload`, {
172
+ method: "POST",
173
+ headers: {
174
+ ...this.authHeaders(),
175
+ "Content-Type": t.type || "application/octet-stream",
176
+ "X-Sticker-Filename": encodeURIComponent(n)
177
+ },
178
+ body: t
179
+ });
180
+ return this.parseResponse(r);
181
+ }
182
+ async updateStickerManifest(e, t) {
183
+ return this.request("PUT", `/api/chat/${e}/stickers/manifest`, { stickers: t });
184
+ }
185
+ async deleteSticker(e, t) {
186
+ return this.request("DELETE", `/api/chat/${e}/stickers/${encodeURIComponent(t)}`);
187
+ }
188
+ async getSounds(e) {
189
+ return this.request("GET", `/api/chat/${encodeURIComponent(e)}/sounds`);
190
+ }
191
+ async getSpaceTheme(e) {
192
+ return this.request("GET", `/api/chat/${e}/theme`);
193
+ }
194
+ async saveSpaceTheme(e, t) {
195
+ return this.request("PUT", `/api/chat/${e}/theme`, t);
196
+ }
197
+ };
198
+ //#endregion
199
+ //#region src/config.ts
200
+ function l() {
201
+ let e = new URLSearchParams(window.location.search), t = e.get("space") ?? e.get("station") ?? "", n = e.get("theme");
202
+ return {
203
+ spaceSlug: t,
204
+ theme: n === "dark" ? "dark" : n === "light" ? "light" : window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light",
205
+ magicLinkToken: e.get("token"),
206
+ embed: e.get("embed") === "true",
207
+ admin: e.get("admin") === "true",
208
+ managed: e.get("managed") === "host"
209
+ };
210
+ }
211
+ var u = typeof window < "u" ? l() : {
212
+ spaceSlug: "",
213
+ theme: "light",
214
+ magicLinkToken: null,
215
+ embed: !1,
216
+ admin: !1,
217
+ managed: !1
218
+ };
219
+ function d() {
220
+ let e = new URL(window.location.href);
221
+ e.searchParams.delete("token"), window.history.replaceState({}, "", e.toString());
222
+ }
223
+ function f(e, t) {
224
+ let n = window.location.protocol === "https:" ? "wss:" : "ws:", r = t ? `token=${encodeURIComponent(t)}&` : "";
225
+ return `${n}//${window.location.port === "5173" && window.location.hostname !== "localhost" ? `${window.location.hostname}:9000` : window.location.host}/ws?${r}station=${encodeURIComponent(e)}`;
226
+ }
227
+ //#endregion
228
+ //#region src/hooks/useRelayaAuth.ts
229
+ var p = "relaya_refresh_token";
230
+ function m(e) {
231
+ try {
232
+ localStorage.setItem(p, e);
233
+ } catch {}
234
+ }
235
+ function h() {
236
+ try {
237
+ return localStorage.getItem(p);
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+ function g() {
243
+ try {
244
+ localStorage.removeItem(p);
245
+ } catch {}
246
+ }
247
+ function _(e) {
248
+ try {
249
+ let [, t] = e.split("."), n = atob(t.replace(/-/g, "+").replace(/_/g, "/"));
250
+ return JSON.parse(n).exp ?? null;
251
+ } catch {
252
+ return null;
253
+ }
254
+ }
255
+ var v = null, y = /* @__PURE__ */ new Map();
256
+ function b(e, t) {
257
+ let n = `${t}:${e}`, r = y.get(n);
258
+ if (r) return r;
259
+ let i = fetch(`/auth/verify?token=${encodeURIComponent(e)}&station=${encodeURIComponent(t)}`).then((e) => e.ok ? e.json() : Promise.reject());
260
+ return y.set(n, i), i.finally(() => {
261
+ window.setTimeout(() => {
262
+ y.delete(n);
263
+ }, 300 * 1e3);
264
+ }).catch(() => {}), i;
265
+ }
266
+ function x(i = {}) {
267
+ let a = i.spaceSlug ?? u.spaceSlug, o = i.initialToken ?? null, s = i.manageOwnRefreshToken ?? !0, l = n(i.onSessionEnded);
268
+ l.current = i.onSessionEnded;
269
+ let [d, f] = r({
270
+ status: "loading",
271
+ user: null,
272
+ token: null,
273
+ station: null,
274
+ stationSlug: a,
275
+ error: null
276
+ }), p = n(null), y = e(() => p.current, []), x = n(null), S = n(null), C = n(new c("", y)).current, w = n(!1), T = e(() => {
277
+ p.current = null, x.current = null, s && g(), S.current !== null && (clearTimeout(S.current), S.current = null), f((e) => ({
278
+ ...e,
279
+ status: "anonymous",
280
+ token: null,
281
+ user: null,
282
+ station: null,
283
+ stationSlug: a
284
+ }));
285
+ }, [a, s]), E = e((e) => {
286
+ S.current !== null && (clearTimeout(S.current), S.current = null);
287
+ let t = _(e);
288
+ if (!t) return;
289
+ let n = Math.max(0, t * 1e3 - Date.now() - 120 * 1e3);
290
+ S.current = setTimeout(() => {
291
+ let e = x.current;
292
+ e && (v ||= C.refresh(e).finally(() => {
293
+ v = null;
294
+ }), v.then((e) => {
295
+ p.current = e.accessToken, x.current = e.refreshToken, s && m(e.refreshToken), f((t) => t.status === "authenticated" ? {
296
+ ...t,
297
+ token: e.accessToken
298
+ } : t), E(e.accessToken);
299
+ }).catch((e) => {
300
+ typeof e == "object" && e && "status" in e && (e.status === 401 || e.status === 403) ? (T(), l.current?.("refresh-failed")) : setTimeout(() => {
301
+ let e = x.current;
302
+ e && C.refresh(e).then((e) => {
303
+ p.current = e.accessToken, x.current = e.refreshToken, s && m(e.refreshToken), f((t) => t.status === "authenticated" ? {
304
+ ...t,
305
+ token: e.accessToken
306
+ } : t), E(e.accessToken);
307
+ }).catch(() => {
308
+ T(), l.current?.("refresh-failed");
309
+ });
310
+ }, 1e4);
311
+ }));
312
+ }, n);
313
+ }, [
314
+ C,
315
+ T,
316
+ s
317
+ ]), D = e(async (e, t) => {
318
+ p.current = e, x.current = t, s && m(t), E(e);
319
+ try {
320
+ let t = await C.getMe(a).catch((e) => {
321
+ throw process.env.NODE_ENV !== "production" && console.warn("[RelayaAuth] getMe failed during token apply", {
322
+ spaceSlug: a,
323
+ err: e
324
+ }), e;
325
+ }), n = await C.getStation(a).catch((e) => {
326
+ throw process.env.NODE_ENV !== "production" && console.warn("[RelayaAuth] getStation failed during token apply", {
327
+ spaceSlug: a,
328
+ err: e
329
+ }), e;
330
+ });
331
+ f({
332
+ status: "authenticated",
333
+ user: {
334
+ id: t.userId,
335
+ displayName: t.displayName,
336
+ avatarUrl: null,
337
+ permissions: t.permissions,
338
+ roles: t.roles,
339
+ chatName: t.chatName
340
+ },
341
+ token: e,
342
+ station: {
343
+ id: n.id,
344
+ name: n.name,
345
+ slug: n.slug
346
+ },
347
+ stationSlug: n.slug,
348
+ error: null
349
+ });
350
+ } catch (e) {
351
+ process.env.NODE_ENV !== "production" && console.warn("[RelayaAuth] Falling back to anonymous after token apply failure", {
352
+ spaceSlug: a,
353
+ err: e
354
+ }), T();
355
+ }
356
+ }, [
357
+ C,
358
+ a,
359
+ E,
360
+ T,
361
+ s
362
+ ]), O = e((e) => {
363
+ D(e.accessToken, e.refreshToken);
364
+ }, [D]);
365
+ t(() => {
366
+ if (w.current) return;
367
+ w.current = !0;
368
+ let e = new URLSearchParams(window.location.search), t = e.get("token"), n = o ?? t, r = e.get("station") || a;
369
+ if (n) {
370
+ if (t) {
371
+ let e = new URL(window.location.href);
372
+ e.searchParams.delete("token"), window.history.replaceState({}, "", e.toString());
373
+ }
374
+ b(n, r).then((e) => {
375
+ D(e.accessToken, e.refreshToken);
376
+ }).catch(() => T());
377
+ } else if (s) {
378
+ let e = h();
379
+ e ? (v ||= C.refresh(e).finally(() => {
380
+ v = null;
381
+ }), v.then((e) => {
382
+ D(e.accessToken, e.refreshToken);
383
+ }).catch((e) => {
384
+ typeof e == "object" && e && "status" in e && (e.status === 401 || e.status === 403) ? (g(), f((e) => ({
385
+ ...e,
386
+ status: "anonymous",
387
+ stationSlug: a
388
+ }))) : setTimeout(() => {
389
+ let e = h();
390
+ if (!e) {
391
+ f((e) => ({
392
+ ...e,
393
+ status: "anonymous",
394
+ stationSlug: a
395
+ }));
396
+ return;
397
+ }
398
+ C.refresh(e).then((e) => void D(e.accessToken, e.refreshToken)).catch(() => {
399
+ g(), f((e) => ({
400
+ ...e,
401
+ status: "anonymous",
402
+ stationSlug: a
403
+ }));
404
+ });
405
+ }, 1e4);
406
+ })) : f((e) => ({
407
+ ...e,
408
+ status: "anonymous",
409
+ stationSlug: a
410
+ }));
411
+ } else f((e) => ({
412
+ ...e,
413
+ status: "anonymous",
414
+ stationSlug: a
415
+ }));
416
+ return () => {
417
+ S.current !== null && clearTimeout(S.current);
418
+ };
419
+ }, []);
420
+ let k = e(async (e) => {
421
+ f((e) => ({
422
+ ...e,
423
+ error: null
424
+ }));
425
+ let t = `${window.location.origin}/auth/popup?station=${encodeURIComponent(a)}`;
426
+ if (!window.open(t, "relaya-auth", "width=480,height=600,left=200,top=100")) {
427
+ f((e) => ({
428
+ ...e,
429
+ error: "Please allow popups for this site to sign in."
430
+ }));
431
+ return;
432
+ }
433
+ let n = (e) => {
434
+ if (e.origin !== window.location.origin || e.data?.type !== "relaya:auth") return;
435
+ window.removeEventListener("message", n);
436
+ let { accessToken: t, refreshToken: r } = e.data;
437
+ D(t, r);
438
+ };
439
+ window.addEventListener("message", n);
440
+ }, [D, a]), A = e(() => {
441
+ let e = x.current ?? (s ? h() : null);
442
+ T(), e && fetch("/auth/logout", {
443
+ method: "POST",
444
+ headers: { "Content-Type": "application/json" },
445
+ body: JSON.stringify({ refreshToken: e })
446
+ }).catch(() => {}), l.current?.("logout");
447
+ }, [T, s]);
448
+ return {
449
+ ...d,
450
+ login: k,
451
+ logout: A,
452
+ getToken: y,
453
+ onOtpVerified: O
454
+ };
455
+ }
456
+ //#endregion
457
+ //#region src/hooks/useSpaceTheme.ts
458
+ var S = [
459
+ "--relaya-color-bg",
460
+ "--relaya-color-message-bg",
461
+ "--relaya-color-message-own-bg",
462
+ "--relaya-color-text",
463
+ "--relaya-color-text-secondary",
464
+ "--relaya-color-input-bg",
465
+ "--relaya-color-input-text",
466
+ "--relaya-color-btn-bg",
467
+ "--relaya-color-btn-text",
468
+ "--relaya-color-name-mod",
469
+ "--relaya-color-link",
470
+ "--relaya-color-link-active"
471
+ ], C = {
472
+ light: {},
473
+ dark: {}
474
+ };
475
+ function w(e, t) {
476
+ let n = document.documentElement;
477
+ S.forEach((e) => n.style.removeProperty(e));
478
+ let r = e[t] ?? {};
479
+ Object.entries(r).forEach(([e, t]) => {
480
+ n.style.setProperty(e, t);
481
+ });
482
+ }
483
+ function T(t, i) {
484
+ let [a, o] = r(C), [s, l] = r(!1), [u, d] = r(!1), [f, p] = r(null), m = n(new c("", i));
485
+ return {
486
+ theme: a,
487
+ loading: s,
488
+ saving: u,
489
+ error: f,
490
+ loadTheme: e(async () => {
491
+ l(!0), p(null);
492
+ try {
493
+ o(await m.current.getSpaceTheme(t));
494
+ } catch (e) {
495
+ p(e?.message ?? "Failed to load theme");
496
+ } finally {
497
+ l(!1);
498
+ }
499
+ }, [t]),
500
+ saveTheme: e(async (e, n) => {
501
+ d(!0), p(null);
502
+ try {
503
+ let r = await m.current.saveSpaceTheme(t, e);
504
+ o(r), w(r, n);
505
+ } catch (e) {
506
+ p(e?.message ?? "Failed to save theme");
507
+ } finally {
508
+ d(!1);
509
+ }
510
+ }, [t]),
511
+ resetTheme: e(async (e) => {
512
+ d(!0), p(null);
513
+ try {
514
+ await m.current.saveSpaceTheme(t, C), o(C), w(C, e);
515
+ } catch (e) {
516
+ p(e?.message ?? "Failed to reset theme");
517
+ } finally {
518
+ d(!1);
519
+ }
520
+ }, [t])
521
+ };
522
+ }
523
+ //#endregion
524
+ //#region src/hooks/useBans.ts
525
+ function E(t, i, a) {
526
+ let [o, l] = r({
527
+ bans: [],
528
+ loading: !1,
529
+ lifting: null,
530
+ error: null
531
+ }), u = i?.permissions.includes(s.BAN_USER) ?? !1, d = n(new c("", a)), f = e(async () => {
532
+ if (!(!u || !t)) {
533
+ l((e) => ({
534
+ ...e,
535
+ loading: !0,
536
+ error: null
537
+ }));
538
+ try {
539
+ let e = (await d.current.getBans(t, !0)).bans;
540
+ l((t) => ({
541
+ ...t,
542
+ bans: e,
543
+ loading: !1
544
+ }));
545
+ } catch (e) {
546
+ l((t) => ({
547
+ ...t,
548
+ loading: !1,
549
+ error: e?.message ?? "Failed to load bans"
550
+ }));
551
+ }
552
+ }
553
+ }, [u, t]), p = e(async (e) => {
554
+ if (u) {
555
+ l((t) => ({
556
+ ...t,
557
+ lifting: e,
558
+ error: null
559
+ }));
560
+ try {
561
+ await d.current.liftBan(t, e), l((e) => ({
562
+ ...e,
563
+ lifting: null
564
+ })), await f();
565
+ } catch (e) {
566
+ throw l((t) => ({
567
+ ...t,
568
+ lifting: null,
569
+ error: e?.message ?? "Failed to lift ban"
570
+ })), e;
571
+ }
572
+ }
573
+ }, [
574
+ u,
575
+ t,
576
+ f
577
+ ]);
578
+ return {
579
+ ...o,
580
+ loadBans: f,
581
+ liftBan: p
582
+ };
583
+ }
584
+ //#endregion
585
+ //#region src/hooks/useModerationConfig.ts
586
+ function D(t, n, i) {
587
+ let [a, o] = r({
588
+ config: null,
589
+ loading: !1,
590
+ saving: !1,
591
+ error: null,
592
+ note: null
593
+ }), l = new c("", i), u = n?.permissions.includes(s.MANAGE_ROLES) ?? !1, d = e(async () => {
594
+ if (!(!u || !t)) {
595
+ o((e) => ({
596
+ ...e,
597
+ loading: !0,
598
+ error: null
599
+ }));
600
+ try {
601
+ let e = await l.getModerationConfig(t);
602
+ o((t) => ({
603
+ ...t,
604
+ config: e.config,
605
+ note: e.note,
606
+ loading: !1
607
+ }));
608
+ } catch (e) {
609
+ o((t) => ({
610
+ ...t,
611
+ loading: !1,
612
+ error: e?.message ?? "Failed to load config"
613
+ }));
614
+ }
615
+ }
616
+ }, [u, t]), f = e(async (e) => {
617
+ if (!(!u || !t)) {
618
+ o((e) => ({
619
+ ...e,
620
+ saving: !0,
621
+ error: null
622
+ }));
623
+ try {
624
+ let n = await l.updateModerationConfig(t, e);
625
+ o((e) => ({
626
+ ...e,
627
+ config: n.config,
628
+ note: n.note,
629
+ saving: !1
630
+ }));
631
+ } catch (e) {
632
+ o((t) => ({
633
+ ...t,
634
+ saving: !1,
635
+ error: e?.message ?? "Failed to update config"
636
+ }));
637
+ }
638
+ }
639
+ }, [u, t]);
640
+ return {
641
+ ...a,
642
+ loadConfig: d,
643
+ updateConfig: f
644
+ };
645
+ }
646
+ function O(t, i, a) {
647
+ let [o, l] = r({
648
+ reports: [],
649
+ total: 0,
650
+ loading: !1,
651
+ actioning: null,
652
+ error: null,
653
+ offset: 0
654
+ }), u = n(0), d = i?.permissions.includes(s.DELETE_ANY) ?? !1, f = n(new c("", a)), p = e(async (e) => {
655
+ if (!d || !t) return;
656
+ let n = e === void 0 ? u.current : e;
657
+ u.current = n, l((e) => ({
658
+ ...e,
659
+ loading: !0,
660
+ error: null
661
+ }));
662
+ try {
663
+ let e = await f.current.getReports(t, {
664
+ status: "pending",
665
+ limit: 20,
666
+ offset: n
667
+ });
668
+ l((t) => ({
669
+ ...t,
670
+ reports: e.reports,
671
+ total: e.total,
672
+ loading: !1,
673
+ offset: n
674
+ }));
675
+ } catch (e) {
676
+ l((t) => ({
677
+ ...t,
678
+ loading: !1,
679
+ error: e?.message ?? "Failed to load reports"
680
+ }));
681
+ }
682
+ }, [d, t]), m = e(async (e) => {
683
+ if (d) {
684
+ l((t) => ({
685
+ ...t,
686
+ actioning: e,
687
+ error: null
688
+ }));
689
+ try {
690
+ await f.current.updateReport(t, e, { status: "dismissed" }), l((e) => ({
691
+ ...e,
692
+ actioning: null
693
+ })), await p();
694
+ } catch (e) {
695
+ throw l((t) => ({
696
+ ...t,
697
+ actioning: null,
698
+ error: e?.message ?? "Failed to dismiss report"
699
+ })), e;
700
+ }
701
+ }
702
+ }, [
703
+ d,
704
+ t,
705
+ p
706
+ ]), h = e(async (e, n) => {
707
+ if (d) {
708
+ l((t) => ({
709
+ ...t,
710
+ actioning: e,
711
+ error: null
712
+ }));
713
+ try {
714
+ try {
715
+ await f.current.deleteMessage(t, n);
716
+ } catch (e) {
717
+ if (e.status !== 404) throw e;
718
+ }
719
+ await f.current.updateReport(t, e, { status: "reviewed" }), l((e) => ({
720
+ ...e,
721
+ actioning: null
722
+ })), await p();
723
+ } catch (e) {
724
+ throw l((t) => ({
725
+ ...t,
726
+ actioning: null,
727
+ error: e?.message ?? "Failed to delete message"
728
+ })), e;
729
+ }
730
+ }
731
+ }, [
732
+ d,
733
+ t,
734
+ p
735
+ ]), g = e(async (e, n, r) => {
736
+ if (d) {
737
+ l((t) => ({
738
+ ...t,
739
+ actioning: e,
740
+ error: null
741
+ }));
742
+ try {
743
+ try {
744
+ await f.current.createBan(t, n, r);
745
+ } catch (e) {
746
+ if (e.status !== 409) throw e;
747
+ }
748
+ await f.current.updateReport(t, e, { status: "reviewed" }), l((e) => ({
749
+ ...e,
750
+ actioning: null
751
+ })), await p();
752
+ } catch (e) {
753
+ throw l((t) => ({
754
+ ...t,
755
+ actioning: null,
756
+ error: e?.message ?? "Failed to ban user"
757
+ })), e;
758
+ }
759
+ }
760
+ }, [
761
+ d,
762
+ t,
763
+ p
764
+ ]);
765
+ return {
766
+ ...o,
767
+ loadReports: p,
768
+ dismissReport: m,
769
+ deleteMessageAndReview: h,
770
+ banAndReview: g
771
+ };
772
+ }
773
+ //#endregion
774
+ //#region src/spaceThemes.ts
775
+ var k = "'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", A = "#7391A7", j = {
776
+ titleBg: A,
777
+ spaceNameColor: "white",
778
+ spaceNameFontFamily: k,
779
+ uiFontFamily: k,
780
+ onlineTextColor: "white",
781
+ noNameTextColor: "white",
782
+ buttonBorderColor: "transparent",
783
+ buttonBg: "white",
784
+ buttonTextColor: "black",
785
+ avatarBg: A,
786
+ avatarTextColor: "white",
787
+ ownMsgBg: A,
788
+ ownMsgText: "white",
789
+ msgFontFamily: k
790
+ }, M = {};
791
+ function N(e) {
792
+ return M[e] ?? j;
793
+ }
794
+ function P(e) {
795
+ let t = document.documentElement, n = (e, n) => {
796
+ n !== void 0 && t.style.setProperty(e, n);
797
+ };
798
+ n("--sp-title-bg", e.titleBg), n("--sp-name-color", e.spaceNameColor), n("--sp-name-font", e.spaceNameFontFamily), n("--sp-ui-font", e.uiFontFamily), n("--sp-online-color", e.onlineTextColor), n("--sp-no-name-color", e.noNameTextColor), n("--sp-btn-border", e.buttonBorderColor), n("--sp-btn-bg", e.buttonBg), n("--sp-btn-text", e.buttonTextColor), n("--sp-avatar-bg", e.avatarBg), n("--sp-avatar-text", e.avatarTextColor), n("--sp-other-msg-bg", e.otherMsgBg), n("--sp-other-msg-text", e.otherMsgText), n("--sp-own-msg-bg", e.ownMsgBg), n("--sp-own-msg-text", e.ownMsgText), n("--sp-msg-font", e.msgFontFamily ?? e.uiFontFamily), n("--sp-msg-font-size", e.msgFontSize), n("--sp-time-color", e.timeLabelColor), n("--sp-send-btn-bg", e.sendButtonBg), n("--sp-send-btn-text", e.sendButtonText);
799
+ }
800
+ //#endregion
801
+ //#region src/stickerAdminUtils.ts
802
+ function F(e, t, n) {
803
+ let r = e.findIndex((e) => e.filename === t), i = e.findIndex((e) => e.filename === n);
804
+ if (r === -1 || i === -1 || r === i) return e;
805
+ let a = [...e], [o] = a.splice(r, 1);
806
+ return a.splice(i, 0, o), a.map((e, t) => ({
807
+ ...e,
808
+ order: t
809
+ }));
810
+ }
811
+ //#endregion
812
+ //#region src/components/BanModal.tsx
813
+ var I = [
814
+ {
815
+ value: "",
816
+ label: "Permanent"
817
+ },
818
+ {
819
+ value: "1h",
820
+ label: "1 hour"
821
+ },
822
+ {
823
+ value: "24h",
824
+ label: "24 hours"
825
+ },
826
+ {
827
+ value: "7d",
828
+ label: "7 days"
829
+ },
830
+ {
831
+ value: "30d",
832
+ label: "30 days"
833
+ }
834
+ ];
835
+ function L(e) {
836
+ if (!e) return;
837
+ let t = Date.now(), n = {
838
+ "1h": 3600 * 1e3,
839
+ "24h": 1440 * 60 * 1e3,
840
+ "7d": 10080 * 60 * 1e3,
841
+ "30d": 720 * 60 * 60 * 1e3
842
+ }[e];
843
+ return n ? new Date(t + n).toISOString() : void 0;
844
+ }
845
+ function R({ userId: e, displayName: t, onBan: n, onClose: i }) {
846
+ let [s, c] = r(""), [l, u] = r(""), [d, f] = r(!1), [p, m] = r(null);
847
+ async function h() {
848
+ f(!0), m(null);
849
+ try {
850
+ await n(e, {
851
+ reason: s.trim() || void 0,
852
+ expiresAt: L(l)
853
+ }), i();
854
+ } catch (e) {
855
+ m(e?.message ?? "Failed to ban user."), f(!1);
856
+ }
857
+ }
858
+ return /* @__PURE__ */ a("div", {
859
+ className: "modal-overlay",
860
+ onClick: (e) => e.target === e.currentTarget && i(),
861
+ children: /* @__PURE__ */ o("div", {
862
+ className: "modal",
863
+ role: "dialog",
864
+ "aria-modal": "true",
865
+ "aria-label": "Ban user",
866
+ children: [
867
+ /* @__PURE__ */ a("h2", {
868
+ className: "modal__title",
869
+ children: "Ban user"
870
+ }),
871
+ /* @__PURE__ */ o("div", {
872
+ className: "modal__body",
873
+ children: [
874
+ /* @__PURE__ */ o("p", { children: [
875
+ "You are about to ban ",
876
+ /* @__PURE__ */ a("strong", { children: t }),
877
+ " from this station's chat. They will be immediately disconnected and will not be able to rejoin."
878
+ ] }),
879
+ /* @__PURE__ */ a("label", {
880
+ htmlFor: "ban-duration",
881
+ children: "Duration"
882
+ }),
883
+ /* @__PURE__ */ a("select", {
884
+ id: "ban-duration",
885
+ value: l,
886
+ onChange: (e) => u(e.target.value),
887
+ disabled: d,
888
+ children: I.map((e) => /* @__PURE__ */ a("option", {
889
+ value: e.value,
890
+ children: e.label
891
+ }, e.value))
892
+ }),
893
+ /* @__PURE__ */ a("label", {
894
+ htmlFor: "ban-reason",
895
+ children: "Reason (optional)"
896
+ }),
897
+ /* @__PURE__ */ a("textarea", {
898
+ id: "ban-reason",
899
+ value: s,
900
+ onChange: (e) => c(e.target.value),
901
+ placeholder: "Why is this user being banned?",
902
+ disabled: d,
903
+ maxLength: 500
904
+ }),
905
+ p && /* @__PURE__ */ a("p", {
906
+ style: {
907
+ color: "var(--color-danger)",
908
+ marginTop: 8
909
+ },
910
+ children: p
911
+ })
912
+ ]
913
+ }),
914
+ /* @__PURE__ */ o("div", {
915
+ className: "modal__footer",
916
+ children: [/* @__PURE__ */ a("button", {
917
+ className: "btn btn--ghost",
918
+ onClick: i,
919
+ disabled: d,
920
+ children: "Cancel"
921
+ }), /* @__PURE__ */ a("button", {
922
+ className: "btn btn--danger",
923
+ onClick: h,
924
+ disabled: d,
925
+ children: d ? "Banning…" : "Confirm ban"
926
+ })]
927
+ })
928
+ ]
929
+ })
930
+ });
931
+ }
932
+ //#endregion
933
+ //#region src/components/ReportReview.tsx
934
+ function z(e) {
935
+ return new Date(e).toLocaleString(void 0, {
936
+ month: "short",
937
+ day: "numeric",
938
+ hour: "2-digit",
939
+ minute: "2-digit"
940
+ });
941
+ }
942
+ function B(e) {
943
+ return e.replace(/_/g, " ");
944
+ }
945
+ function V({ stationSlug: e, user: n, getToken: i }) {
946
+ let [c, l] = r(!1), [u, d] = r(null), { reports: f, total: p, loading: m, actioning: h, error: g, offset: _, loadReports: v, dismissReport: y, deleteMessageAndReview: b, banAndReview: x } = O(e, n, i);
947
+ if (t(() => {
948
+ c && v(0);
949
+ }, [c]), !n.permissions.includes(s.DELETE_ANY)) return null;
950
+ let S = _ > 0, C = _ + 20 < p;
951
+ return /* @__PURE__ */ o("div", {
952
+ className: "report-review",
953
+ children: [
954
+ /* @__PURE__ */ o("button", {
955
+ className: "report-review__toggle",
956
+ onClick: () => l((e) => !e),
957
+ "aria-expanded": c,
958
+ children: [/* @__PURE__ */ a("span", { children: c ? "▼" : "▶" }), /* @__PURE__ */ o("span", { children: ["Report queue", p > 0 && /* @__PURE__ */ a("span", {
959
+ className: "report-review__badge",
960
+ "aria-label": `${p} pending reports`,
961
+ children: p
962
+ })] })]
963
+ }),
964
+ c && /* @__PURE__ */ o("div", {
965
+ className: "report-review__panel",
966
+ children: [
967
+ g && /* @__PURE__ */ a("p", {
968
+ className: "report-review__error",
969
+ children: g
970
+ }),
971
+ m && /* @__PURE__ */ a("p", {
972
+ className: "report-review__loading",
973
+ children: "Loading…"
974
+ }),
975
+ !m && f.length === 0 && /* @__PURE__ */ a("p", {
976
+ className: "report-review__empty",
977
+ children: "No pending reports. ✓"
978
+ }),
979
+ f.map((e) => {
980
+ let t = h === e.reportId;
981
+ return /* @__PURE__ */ o("div", {
982
+ className: "report-card",
983
+ children: [
984
+ /* @__PURE__ */ o("div", {
985
+ className: "report-card__meta",
986
+ children: [/* @__PURE__ */ a("span", {
987
+ className: "report-card__reason",
988
+ children: B(e.reason)
989
+ }), /* @__PURE__ */ a("span", {
990
+ className: "report-card__time",
991
+ children: z(e.createdAt)
992
+ })]
993
+ }),
994
+ /* @__PURE__ */ a("div", {
995
+ className: "report-card__message",
996
+ children: e.messageIsDeleted ? /* @__PURE__ */ a("em", {
997
+ className: "report-card__deleted",
998
+ children: "[message already deleted]"
999
+ }) : /* @__PURE__ */ a("span", { children: e.messageContent ?? "" })
1000
+ }),
1001
+ /* @__PURE__ */ o("div", {
1002
+ className: "report-card__details",
1003
+ children: [
1004
+ /* @__PURE__ */ o("span", { children: [
1005
+ /* @__PURE__ */ a("strong", { children: "Author:" }),
1006
+ " ",
1007
+ e.messageAuthor.displayName
1008
+ ] }),
1009
+ /* @__PURE__ */ o("span", { children: [
1010
+ /* @__PURE__ */ a("strong", { children: "Reported by:" }),
1011
+ " ",
1012
+ e.reporter.displayName
1013
+ ] }),
1014
+ e.details && /* @__PURE__ */ o("span", { children: [
1015
+ /* @__PURE__ */ a("strong", { children: "Note:" }),
1016
+ " ",
1017
+ e.details
1018
+ ] })
1019
+ ]
1020
+ }),
1021
+ /* @__PURE__ */ o("div", {
1022
+ className: "report-card__actions",
1023
+ children: [
1024
+ /* @__PURE__ */ a("button", {
1025
+ className: "btn btn--ghost",
1026
+ style: {
1027
+ fontSize: 12,
1028
+ padding: "3px 10px"
1029
+ },
1030
+ onClick: () => y(e.reportId),
1031
+ disabled: t,
1032
+ title: "Dismiss this report — no action taken",
1033
+ children: t ? "…" : "Dismiss"
1034
+ }),
1035
+ !e.messageIsDeleted && /* @__PURE__ */ a("button", {
1036
+ className: "btn btn--ghost",
1037
+ style: {
1038
+ fontSize: 12,
1039
+ padding: "3px 10px"
1040
+ },
1041
+ onClick: () => b(e.reportId, e.messageId),
1042
+ disabled: t,
1043
+ title: "Delete the reported message and mark this report reviewed",
1044
+ children: t ? "…" : "Delete message"
1045
+ }),
1046
+ /* @__PURE__ */ a("button", {
1047
+ className: "btn btn--danger",
1048
+ style: {
1049
+ fontSize: 12,
1050
+ padding: "3px 10px"
1051
+ },
1052
+ onClick: () => d({
1053
+ reportId: e.reportId,
1054
+ userId: e.messageAuthor.userId,
1055
+ displayName: e.messageAuthor.displayName
1056
+ }),
1057
+ disabled: t,
1058
+ title: "Ban this message's author and mark this report reviewed",
1059
+ children: "Ban author"
1060
+ })
1061
+ ]
1062
+ })
1063
+ ]
1064
+ }, e.reportId);
1065
+ }),
1066
+ (S || C) && /* @__PURE__ */ o("div", {
1067
+ className: "report-review__pagination",
1068
+ children: [
1069
+ /* @__PURE__ */ a("button", {
1070
+ className: "btn btn--ghost",
1071
+ style: {
1072
+ fontSize: 12,
1073
+ padding: "3px 10px"
1074
+ },
1075
+ onClick: () => v(_ - 20),
1076
+ disabled: !S || m,
1077
+ children: "← Previous"
1078
+ }),
1079
+ /* @__PURE__ */ o("span", {
1080
+ className: "report-review__page-info",
1081
+ children: [
1082
+ _ + 1,
1083
+ "–",
1084
+ Math.min(_ + 20, p),
1085
+ " of ",
1086
+ p
1087
+ ]
1088
+ }),
1089
+ /* @__PURE__ */ a("button", {
1090
+ className: "btn btn--ghost",
1091
+ style: {
1092
+ fontSize: 12,
1093
+ padding: "3px 10px"
1094
+ },
1095
+ onClick: () => v(_ + 20),
1096
+ disabled: !C || m,
1097
+ children: "Next →"
1098
+ })
1099
+ ]
1100
+ })
1101
+ ]
1102
+ }),
1103
+ u && /* @__PURE__ */ a(R, {
1104
+ userId: u.userId,
1105
+ displayName: u.displayName,
1106
+ onBan: async (e, t) => {
1107
+ await x(u.reportId, e, t);
1108
+ },
1109
+ onClose: () => d(null)
1110
+ })
1111
+ ]
1112
+ });
1113
+ }
1114
+ //#endregion
1115
+ //#region src/components/BanManagement.tsx
1116
+ function H(e) {
1117
+ return new Date(e).toLocaleString(void 0, {
1118
+ month: "short",
1119
+ day: "numeric",
1120
+ hour: "2-digit",
1121
+ minute: "2-digit"
1122
+ });
1123
+ }
1124
+ function U(e, t) {
1125
+ return t || !e ? "Permanent" : new Date(e) < /* @__PURE__ */ new Date() ? "Expired" : `Expires ${H(e)}`;
1126
+ }
1127
+ function W({ stationSlug: e, user: n, getToken: i }) {
1128
+ let [c, l] = r(!1), { bans: u, loading: d, lifting: f, error: p, loadBans: m, liftBan: h } = E(e, n, i);
1129
+ return t(() => {
1130
+ c && m();
1131
+ }, [c]), n.permissions.includes(s.BAN_USER) ? /* @__PURE__ */ o("div", {
1132
+ className: "ban-management",
1133
+ children: [/* @__PURE__ */ o("button", {
1134
+ className: "ban-management__toggle",
1135
+ onClick: () => l((e) => !e),
1136
+ "aria-expanded": c,
1137
+ children: [/* @__PURE__ */ a("span", { children: c ? "▼" : "▶" }), /* @__PURE__ */ o("span", { children: ["Active bans", u.length > 0 && /* @__PURE__ */ a("span", {
1138
+ className: "ban-management__badge",
1139
+ "aria-label": `${u.length} active bans`,
1140
+ children: u.length
1141
+ })] })]
1142
+ }), c && /* @__PURE__ */ o("div", {
1143
+ className: "ban-management__panel",
1144
+ children: [
1145
+ p && /* @__PURE__ */ a("p", {
1146
+ className: "ban-management__error",
1147
+ children: p
1148
+ }),
1149
+ d && /* @__PURE__ */ a("p", {
1150
+ className: "ban-management__loading",
1151
+ children: "Loading…"
1152
+ }),
1153
+ !d && u.length === 0 && /* @__PURE__ */ a("p", {
1154
+ className: "ban-management__empty",
1155
+ children: "No active bans. ✓"
1156
+ }),
1157
+ u.map((e) => {
1158
+ let t = f === e.banId;
1159
+ return /* @__PURE__ */ o("div", {
1160
+ className: "ban-card",
1161
+ children: [
1162
+ /* @__PURE__ */ o("div", {
1163
+ className: "ban-card__header",
1164
+ children: [/* @__PURE__ */ a("span", {
1165
+ className: "ban-card__user",
1166
+ children: e.user.displayName
1167
+ }), /* @__PURE__ */ a("span", {
1168
+ className: `ban-card__expiry${e.isPermanent ? " ban-card__expiry--permanent" : ""}`,
1169
+ children: U(e.expiresAt, e.isPermanent)
1170
+ })]
1171
+ }),
1172
+ /* @__PURE__ */ o("div", {
1173
+ className: "ban-card__details",
1174
+ children: [
1175
+ e.reason && /* @__PURE__ */ o("span", { children: [
1176
+ /* @__PURE__ */ a("strong", { children: "Reason:" }),
1177
+ " ",
1178
+ e.reason
1179
+ ] }),
1180
+ /* @__PURE__ */ o("span", { children: [
1181
+ /* @__PURE__ */ a("strong", { children: "Banned by:" }),
1182
+ " ",
1183
+ e.bannedBy.displayName
1184
+ ] }),
1185
+ /* @__PURE__ */ o("span", { children: [
1186
+ /* @__PURE__ */ a("strong", { children: "Banned at:" }),
1187
+ " ",
1188
+ H(e.createdAt)
1189
+ ] })
1190
+ ]
1191
+ }),
1192
+ /* @__PURE__ */ a("div", {
1193
+ className: "ban-card__actions",
1194
+ children: /* @__PURE__ */ a("button", {
1195
+ className: "btn btn--ghost",
1196
+ style: {
1197
+ fontSize: 12,
1198
+ padding: "3px 10px"
1199
+ },
1200
+ onClick: () => h(e.banId),
1201
+ disabled: t,
1202
+ title: "Remove this ban and allow the user to rejoin",
1203
+ children: t ? "…" : "Lift ban"
1204
+ })
1205
+ })
1206
+ ]
1207
+ }, e.banId);
1208
+ })
1209
+ ]
1210
+ })]
1211
+ }) : null;
1212
+ }
1213
+ //#endregion
1214
+ //#region src/hooks/usePresenceConfig.ts
1215
+ function G(t, n, i) {
1216
+ let [a, o] = r({
1217
+ config: null,
1218
+ loading: !1,
1219
+ saving: !1,
1220
+ error: null
1221
+ }), l = new c("", i), u = n?.permissions.includes(s.MANAGE_ROLES) ?? !1, d = e(async () => {
1222
+ if (!(!u || !t)) {
1223
+ o((e) => ({
1224
+ ...e,
1225
+ loading: !0,
1226
+ error: null
1227
+ }));
1228
+ try {
1229
+ let e = await l.getPresenceConfig(t);
1230
+ o((t) => ({
1231
+ ...t,
1232
+ config: e.config,
1233
+ loading: !1
1234
+ }));
1235
+ } catch (e) {
1236
+ o((t) => ({
1237
+ ...t,
1238
+ loading: !1,
1239
+ error: e?.message ?? "Failed to load presence config"
1240
+ }));
1241
+ }
1242
+ }
1243
+ }, [u, t]), f = e(async (e) => {
1244
+ if (!(!u || !t)) {
1245
+ o((e) => ({
1246
+ ...e,
1247
+ saving: !0,
1248
+ error: null
1249
+ }));
1250
+ try {
1251
+ let n = await l.updatePresenceConfig(t, e);
1252
+ o((e) => ({
1253
+ ...e,
1254
+ config: n.config,
1255
+ saving: !1
1256
+ }));
1257
+ } catch (e) {
1258
+ o((t) => ({
1259
+ ...t,
1260
+ saving: !1,
1261
+ error: e?.message ?? "Failed to update presence config"
1262
+ }));
1263
+ }
1264
+ }
1265
+ }, [u, t]);
1266
+ return {
1267
+ ...a,
1268
+ loadConfig: d,
1269
+ updateConfig: f
1270
+ };
1271
+ }
1272
+ //#endregion
1273
+ //#region src/components/AdminSettings.tsx
1274
+ function K({ stationSlug: e, user: n, getToken: s, onOpenStickerAdmin: c, onOpenThemeAdmin: l }) {
1275
+ let [u, d] = r(!1), { config: f, loading: p, saving: m, error: h, note: g, loadConfig: _, updateConfig: v } = D(e, n, s), [y, b] = r(null), { config: x, loading: S, saving: C, error: w, loadConfig: T, updateConfig: E } = G(e, n, s), [O, k] = r("");
1276
+ t(() => {
1277
+ u && (!f && !p && _(), !x && !S && T());
1278
+ }, [u]), t(() => {
1279
+ f && !y && b({
1280
+ rateLimitWindowMs: String(f.rateLimitWindowMs),
1281
+ rateLimitMaxMessages: String(f.rateLimitMaxMessages),
1282
+ duplicateWindowMs: String(f.duplicateWindowMs)
1283
+ });
1284
+ }, [f]), t(() => {
1285
+ if (x && O === "") {
1286
+ let e = x.presenceGracePeriodMs / 6e4;
1287
+ k(String(Number(e.toFixed(1))));
1288
+ }
1289
+ }, [x]);
1290
+ async function A() {
1291
+ y && await v({
1292
+ rateLimitWindowMs: parseInt(y.rateLimitWindowMs, 10),
1293
+ rateLimitMaxMessages: parseInt(y.rateLimitMaxMessages, 10),
1294
+ duplicateWindowMs: parseInt(y.duplicateWindowMs, 10)
1295
+ });
1296
+ }
1297
+ async function j() {
1298
+ let e = parseFloat(O);
1299
+ isNaN(e) || e < 0 || e > 15 || await E({ presenceGracePeriodMs: Math.round(e * 6e4 / 1e3) * 1e3 });
1300
+ }
1301
+ return /* @__PURE__ */ o("div", {
1302
+ className: "admin-settings",
1303
+ children: [/* @__PURE__ */ o("button", {
1304
+ className: "admin-settings__toggle",
1305
+ onClick: () => d((e) => !e),
1306
+ children: [/* @__PURE__ */ a("span", { children: u ? "▼" : "▶" }), /* @__PURE__ */ a("span", { children: "Moderation settings" })]
1307
+ }), u && /* @__PURE__ */ o("div", {
1308
+ className: "admin-settings__panel",
1309
+ children: [
1310
+ c && /* @__PURE__ */ o("div", {
1311
+ className: "admin-settings__section",
1312
+ children: [/* @__PURE__ */ o("div", {
1313
+ className: "admin-settings__section-copy",
1314
+ children: [/* @__PURE__ */ a("strong", { children: "Sticker library" }), /* @__PURE__ */ a("span", { children: "Upload stickers, assign shortcodes, and control picker order." })]
1315
+ }), /* @__PURE__ */ a("button", {
1316
+ className: "btn btn--ghost",
1317
+ style: {
1318
+ width: "auto",
1319
+ padding: "6px 16px"
1320
+ },
1321
+ onClick: c,
1322
+ children: "Open sticker manager"
1323
+ })]
1324
+ }),
1325
+ l && /* @__PURE__ */ o("div", {
1326
+ className: "admin-settings__section",
1327
+ children: [/* @__PURE__ */ o("div", {
1328
+ className: "admin-settings__section-copy",
1329
+ children: [/* @__PURE__ */ a("strong", { children: "Colour theme" }), /* @__PURE__ */ a("span", { children: "Customise the chat colours to match your brand." })]
1330
+ }), /* @__PURE__ */ a("button", {
1331
+ className: "btn btn--ghost",
1332
+ style: {
1333
+ width: "auto",
1334
+ padding: "6px 16px"
1335
+ },
1336
+ onClick: l,
1337
+ children: "Open theme editor"
1338
+ })]
1339
+ }),
1340
+ /* @__PURE__ */ o("div", {
1341
+ className: "admin-settings__notice",
1342
+ children: ["⚠️ Changes apply immediately but revert to environment defaults on server restart.", g && /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ a("br", {}), g] })]
1343
+ }),
1344
+ p && /* @__PURE__ */ a("p", {
1345
+ style: {
1346
+ fontSize: 13,
1347
+ color: "var(--color-text-muted)"
1348
+ },
1349
+ children: "Loading…"
1350
+ }),
1351
+ h && /* @__PURE__ */ a("p", {
1352
+ style: {
1353
+ fontSize: 13,
1354
+ color: "var(--color-danger)"
1355
+ },
1356
+ children: h
1357
+ }),
1358
+ y && /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ o("div", {
1359
+ className: "admin-settings__grid",
1360
+ children: [
1361
+ /* @__PURE__ */ o("div", {
1362
+ className: "admin-settings__field",
1363
+ children: [/* @__PURE__ */ a("label", {
1364
+ htmlFor: "rl-window",
1365
+ children: "Rate-limit window (ms)"
1366
+ }), /* @__PURE__ */ a("input", {
1367
+ id: "rl-window",
1368
+ type: "number",
1369
+ min: 1e3,
1370
+ value: y.rateLimitWindowMs,
1371
+ onChange: (e) => b((t) => t && {
1372
+ ...t,
1373
+ rateLimitWindowMs: e.target.value
1374
+ })
1375
+ })]
1376
+ }),
1377
+ /* @__PURE__ */ o("div", {
1378
+ className: "admin-settings__field",
1379
+ children: [/* @__PURE__ */ a("label", {
1380
+ htmlFor: "rl-max",
1381
+ children: "Max messages per window"
1382
+ }), /* @__PURE__ */ a("input", {
1383
+ id: "rl-max",
1384
+ type: "number",
1385
+ min: 1,
1386
+ value: y.rateLimitMaxMessages,
1387
+ onChange: (e) => b((t) => t && {
1388
+ ...t,
1389
+ rateLimitMaxMessages: e.target.value
1390
+ })
1391
+ })]
1392
+ }),
1393
+ /* @__PURE__ */ o("div", {
1394
+ className: "admin-settings__field",
1395
+ children: [/* @__PURE__ */ a("label", {
1396
+ htmlFor: "dup-window",
1397
+ children: "Duplicate burst window (ms)"
1398
+ }), /* @__PURE__ */ a("input", {
1399
+ id: "dup-window",
1400
+ type: "number",
1401
+ min: 0,
1402
+ value: y.duplicateWindowMs,
1403
+ onChange: (e) => b((t) => t && {
1404
+ ...t,
1405
+ duplicateWindowMs: e.target.value
1406
+ })
1407
+ })]
1408
+ })
1409
+ ]
1410
+ }), /* @__PURE__ */ a("button", {
1411
+ className: "btn btn--primary",
1412
+ style: {
1413
+ width: "auto",
1414
+ padding: "6px 16px"
1415
+ },
1416
+ onClick: A,
1417
+ disabled: m,
1418
+ children: m ? "Saving…" : "Apply changes"
1419
+ })] }),
1420
+ /* @__PURE__ */ o("div", {
1421
+ className: "admin-settings__divider",
1422
+ style: {
1423
+ marginTop: 20,
1424
+ borderTop: "1px solid var(--color-border)",
1425
+ paddingTop: 16
1426
+ },
1427
+ children: [
1428
+ /* @__PURE__ */ a("strong", {
1429
+ style: { fontSize: 13 },
1430
+ children: "Online status grace period"
1431
+ }),
1432
+ /* @__PURE__ */ a("p", {
1433
+ style: {
1434
+ fontSize: 12,
1435
+ color: "var(--color-text-muted)",
1436
+ marginTop: 4,
1437
+ marginBottom: 10
1438
+ },
1439
+ children: "How long a user stays visible in the online list after their browser disconnects. Use 0 for immediate removal. Saved to the database — persists across restarts."
1440
+ }),
1441
+ S && /* @__PURE__ */ a("p", {
1442
+ style: {
1443
+ fontSize: 13,
1444
+ color: "var(--color-text-muted)"
1445
+ },
1446
+ children: "Loading…"
1447
+ }),
1448
+ w && /* @__PURE__ */ a("p", {
1449
+ style: {
1450
+ fontSize: 13,
1451
+ color: "var(--color-danger)"
1452
+ },
1453
+ children: w
1454
+ }),
1455
+ x !== null && /* @__PURE__ */ o("div", {
1456
+ style: {
1457
+ display: "flex",
1458
+ alignItems: "center",
1459
+ gap: 8
1460
+ },
1461
+ children: [
1462
+ /* @__PURE__ */ a("input", {
1463
+ id: "presence-grace",
1464
+ type: "number",
1465
+ min: 0,
1466
+ max: 15,
1467
+ step: .5,
1468
+ value: O,
1469
+ onChange: (e) => k(e.target.value),
1470
+ style: { width: 80 }
1471
+ }),
1472
+ /* @__PURE__ */ a("label", {
1473
+ htmlFor: "presence-grace",
1474
+ style: { fontSize: 13 },
1475
+ children: "minutes"
1476
+ }),
1477
+ /* @__PURE__ */ a("button", {
1478
+ className: "btn btn--primary",
1479
+ style: {
1480
+ width: "auto",
1481
+ padding: "6px 16px"
1482
+ },
1483
+ onClick: j,
1484
+ disabled: C,
1485
+ children: C ? "Saving…" : "Save"
1486
+ })
1487
+ ]
1488
+ })
1489
+ ]
1490
+ })
1491
+ ]
1492
+ })]
1493
+ });
1494
+ }
1495
+ //#endregion
1496
+ //#region src/components/StickerAdminPage.tsx
1497
+ var q = 2 * 1024 * 1024, J = 310;
1498
+ function ee(e) {
1499
+ return e.shortcode ? `:${e.shortcode}:` : e.filename;
1500
+ }
1501
+ async function te(e) {
1502
+ let t = URL.createObjectURL(e);
1503
+ try {
1504
+ let e = await new Promise((e, n) => {
1505
+ let r = new Image();
1506
+ r.onload = () => e(r), r.onerror = () => n(/* @__PURE__ */ Error("Unable to read image dimensions")), r.src = t;
1507
+ });
1508
+ return {
1509
+ width: e.naturalWidth,
1510
+ height: e.naturalHeight
1511
+ };
1512
+ } finally {
1513
+ URL.revokeObjectURL(t);
1514
+ }
1515
+ }
1516
+ function Y({ stationSlug: e, user: i, getToken: l, onClose: u, onLibraryChanged: d }) {
1517
+ let f = n(new c("", l)).current, p = n(null), [m, h] = r([]), [g, _] = r(null), [v, y] = r(!0), [b, x] = r(!1), [S, C] = r(!1), [w, T] = r(null), [E, D] = r(!1), [O, k] = r(null), [A, j] = r(null), [M, N] = r(null), [P, I] = r(null), L = i.permissions.includes(s.MANAGE_ROLES);
1518
+ async function R() {
1519
+ y(!0), k(null), j(null);
1520
+ try {
1521
+ let t = await f.getStickers(e);
1522
+ h(t.stickers ?? []), _(t.quota ?? null), D(!1);
1523
+ } catch (e) {
1524
+ k(e?.message ?? "Failed to load sticker library.");
1525
+ } finally {
1526
+ y(!1);
1527
+ }
1528
+ }
1529
+ if (t(() => {
1530
+ L && R().catch(() => void 0);
1531
+ }, [L, e]), t(() => {
1532
+ if (A !== "Sticker order saved.") return;
1533
+ let e = window.setTimeout(() => {
1534
+ j((e) => e === "Sticker order saved." ? null : e);
1535
+ }, 1800);
1536
+ return () => window.clearTimeout(e);
1537
+ }, [A]), !L) return null;
1538
+ function z() {
1539
+ N(null), I(null);
1540
+ }
1541
+ function B(e, t) {
1542
+ if (b || S || w) {
1543
+ e.preventDefault();
1544
+ return;
1545
+ }
1546
+ N(t), e.dataTransfer.effectAllowed = "move", e.dataTransfer.setData("text/plain", t);
1547
+ }
1548
+ function V(e, t) {
1549
+ !M || M === t || (e.preventDefault(), e.dataTransfer.dropEffect = "move", P !== t && I(t));
1550
+ }
1551
+ function H(e, t) {
1552
+ e.preventDefault();
1553
+ let n = M || e.dataTransfer.getData("text/plain");
1554
+ if (!n || n === t) {
1555
+ z();
1556
+ return;
1557
+ }
1558
+ let r = F(m, n, t);
1559
+ if (r === m) {
1560
+ z();
1561
+ return;
1562
+ }
1563
+ h(r), D(!0), j(null), z(), W(r, "Sticker order saved.");
1564
+ }
1565
+ function U(e, t) {
1566
+ h((n) => n.map((n) => n.filename === e ? {
1567
+ ...n,
1568
+ shortcode: t
1569
+ } : n)), D(!0), j(null);
1570
+ }
1571
+ async function W(t, n) {
1572
+ x(!0), k(null), j(null);
1573
+ try {
1574
+ let r = await f.updateStickerManifest(e, t.map((e) => ({
1575
+ filename: e.filename,
1576
+ shortcode: e.shortcode?.trim() || null
1577
+ })));
1578
+ return h(r.stickers ?? []), D(!1), j(n), await d?.(), r.stickers ?? [];
1579
+ } catch (e) {
1580
+ return k(e?.message ?? "Failed to save sticker metadata."), D(!0), null;
1581
+ } finally {
1582
+ x(!1);
1583
+ }
1584
+ }
1585
+ async function G() {
1586
+ await W(m, "Sticker metadata saved.");
1587
+ }
1588
+ async function K(t) {
1589
+ let n = t.target.files?.[0];
1590
+ if (t.target.value = "", n) {
1591
+ if (E) {
1592
+ k("Save or discard your existing sticker edits before uploading a new file.");
1593
+ return;
1594
+ }
1595
+ if (n.size > q) {
1596
+ k("Sticker files must be 2MB or smaller.");
1597
+ return;
1598
+ }
1599
+ try {
1600
+ let e = await te(n);
1601
+ if (Math.max(e.width, e.height) > J) {
1602
+ k(`Sticker dimensions must be ${J}px or smaller on the longest side.`);
1603
+ return;
1604
+ }
1605
+ } catch {
1606
+ k("Could not read the selected image.");
1607
+ return;
1608
+ }
1609
+ C(!0), k(null), j(null);
1610
+ try {
1611
+ let t = await f.uploadSticker(e, n, n.name);
1612
+ h((e) => [...e, t.sticker].sort((e, t) => e.order - t.order || e.filename.localeCompare(t.filename))), _(t.quota ?? null), j("Sticker uploaded. Assign a shortcode and save when you are ready."), await d?.();
1613
+ } catch (e) {
1614
+ k(e?.message ?? "Failed to upload sticker.");
1615
+ } finally {
1616
+ C(!1);
1617
+ }
1618
+ }
1619
+ }
1620
+ async function Y(t) {
1621
+ if (E) {
1622
+ k("Save or discard your existing sticker edits before deleting a file.");
1623
+ return;
1624
+ }
1625
+ if (window.confirm(`Delete ${t}? This removes the sticker file and its shortcode mapping.`)) {
1626
+ T(t), k(null), j(null);
1627
+ try {
1628
+ await f.deleteSticker(e, t), await R(), j("Sticker deleted."), await d?.();
1629
+ } catch (e) {
1630
+ k(e?.message ?? "Failed to delete sticker.");
1631
+ } finally {
1632
+ T(null);
1633
+ }
1634
+ }
1635
+ }
1636
+ return /* @__PURE__ */ o("div", {
1637
+ className: "sticker-admin-page",
1638
+ children: [
1639
+ /* @__PURE__ */ o("div", {
1640
+ className: "sticker-admin-page__topbar",
1641
+ children: [/* @__PURE__ */ o("div", {
1642
+ className: "sticker-admin-page__title-group",
1643
+ children: [/* @__PURE__ */ o("div", {
1644
+ className: "sticker-admin-page__title-row",
1645
+ children: [
1646
+ u && /* @__PURE__ */ a("button", {
1647
+ className: "btn btn--ghost sticker-admin-page__back",
1648
+ onClick: u,
1649
+ children: "← Back"
1650
+ }),
1651
+ /* @__PURE__ */ a("h2", {
1652
+ className: "sticker-admin-page__title",
1653
+ children: "Sticker library"
1654
+ }),
1655
+ /* @__PURE__ */ a("span", {
1656
+ className: "sticker-admin-page__count",
1657
+ children: g ? `${g.used} sticker${g.used === 1 ? "" : "s"} of ${g.limit >= 9999 ? "unlimited" : g.limit} slots available` : `${m.length} sticker${m.length === 1 ? "" : "s"}`
1658
+ }),
1659
+ A && /* @__PURE__ */ a("div", {
1660
+ className: "sticker-admin-page__status-inline",
1661
+ children: A
1662
+ })
1663
+ ]
1664
+ }), /* @__PURE__ */ a("p", {
1665
+ className: "sticker-admin-page__subtitle",
1666
+ children: "Upload stickers, assign shortcodes, and order the picker."
1667
+ })]
1668
+ }), /* @__PURE__ */ o("div", {
1669
+ className: "sticker-admin-page__header-actions",
1670
+ children: [
1671
+ /* @__PURE__ */ a("button", {
1672
+ className: "btn btn--ghost",
1673
+ onClick: () => p.current?.click(),
1674
+ disabled: S || b || v,
1675
+ children: S ? "Uploading…" : "Upload"
1676
+ }),
1677
+ /* @__PURE__ */ a("button", {
1678
+ className: "btn btn--ghost",
1679
+ onClick: () => R(),
1680
+ disabled: v || b || S,
1681
+ children: "Reload"
1682
+ }),
1683
+ /* @__PURE__ */ a("button", {
1684
+ className: "btn btn--primary",
1685
+ onClick: G,
1686
+ disabled: !E || b || v || S,
1687
+ children: b ? "Saving…" : "Save shortcodes"
1688
+ })
1689
+ ]
1690
+ })]
1691
+ }),
1692
+ /* @__PURE__ */ o("div", {
1693
+ className: "sticker-admin-page__notice",
1694
+ children: [/* @__PURE__ */ a("strong", { children: "Rules:" }), " unique per station, max 12 chars, lowercase letters/numbers/hyphens/underscores only. Blank shortcode fields auto-generate from the filename. Uploads ≤2MB and ≤310px. Drag tiles to reorder."]
1695
+ }),
1696
+ O && /* @__PURE__ */ a("div", {
1697
+ className: "sticker-admin-page__error",
1698
+ children: O
1699
+ }),
1700
+ /* @__PURE__ */ a("input", {
1701
+ ref: p,
1702
+ type: "file",
1703
+ accept: "image/gif,image/png,image/jpeg,image/webp",
1704
+ hidden: !0,
1705
+ onChange: K
1706
+ }),
1707
+ v ? /* @__PURE__ */ a("div", {
1708
+ className: "sticker-admin-page__state",
1709
+ children: "Loading sticker library…"
1710
+ }) : m.length === 0 ? /* @__PURE__ */ a("div", {
1711
+ className: "sticker-admin-page__state",
1712
+ children: "No stickers uploaded yet."
1713
+ }) : /* @__PURE__ */ a("div", {
1714
+ className: "sticker-admin-grid",
1715
+ children: m.map((e, t) => {
1716
+ let n = w === e.filename, r = M === e.filename, i = P === e.filename && M !== e.filename;
1717
+ return /* @__PURE__ */ o("div", {
1718
+ className: [
1719
+ "sticker-admin-card",
1720
+ r ? "sticker-admin-card--dragging" : "",
1721
+ i ? "sticker-admin-card--drop-target" : ""
1722
+ ].filter(Boolean).join(" "),
1723
+ onDragOver: (t) => V(t, e.filename),
1724
+ onDrop: (t) => H(t, e.filename),
1725
+ children: [/* @__PURE__ */ o("div", {
1726
+ className: "sticker-admin-card__preview sticker-admin-card__preview--draggable",
1727
+ draggable: !b && !S && !w,
1728
+ onDragStart: (t) => B(t, e.filename),
1729
+ onDragEnd: z,
1730
+ title: "Drag to reorder",
1731
+ children: [/* @__PURE__ */ a("img", {
1732
+ src: e.url,
1733
+ alt: ee(e),
1734
+ loading: "lazy",
1735
+ draggable: !1
1736
+ }), /* @__PURE__ */ o("span", {
1737
+ className: "sticker-admin-card__drag-badge",
1738
+ "aria-hidden": "true",
1739
+ children: [/* @__PURE__ */ a("svg", {
1740
+ viewBox: "0 0 24 24",
1741
+ focusable: "false",
1742
+ children: /* @__PURE__ */ a("path", {
1743
+ fill: "currentColor",
1744
+ d: "M11 23v-3.17l-1.41 1.41-1.42-1.41L12 16l3.83 3.83-1.42 1.41L13 19.83V23h-2Zm-3-7-3.83-3.83 3.83-3.83 1.42 1.41L8.01 11H11v2H8.01l1.41 1.41L8 16Zm8 0-1.41-1.41L15.99 13H13v-2h2.99l-1.4-1.42L16 8.17 19.83 12 16 15.83ZM12 8 8.17 4.17l1.42-1.41L11 4.17V1h2v3.17l1.41-1.41 1.42 1.41L12 8Z"
1745
+ })
1746
+ }), /* @__PURE__ */ a("span", { children: "Drag to reorder" })]
1747
+ })]
1748
+ }), /* @__PURE__ */ o("div", {
1749
+ className: "sticker-admin-card__body",
1750
+ children: [
1751
+ /* @__PURE__ */ a("div", {
1752
+ className: "sticker-admin-card__filename",
1753
+ title: e.filename,
1754
+ children: e.filename
1755
+ }),
1756
+ /* @__PURE__ */ a("label", {
1757
+ className: "sticker-admin-card__field",
1758
+ children: /* @__PURE__ */ o("div", {
1759
+ className: "sticker-admin-card__shortcode-input",
1760
+ children: [
1761
+ /* @__PURE__ */ a("span", { children: ":" }),
1762
+ /* @__PURE__ */ a("input", {
1763
+ type: "text",
1764
+ value: e.shortcode ?? "",
1765
+ placeholder: "viking",
1766
+ maxLength: 12,
1767
+ "aria-label": `Shortcode for ${e.filename}`,
1768
+ onChange: (t) => U(e.filename, t.target.value.toLowerCase())
1769
+ }),
1770
+ /* @__PURE__ */ a("span", { children: ":" })
1771
+ ]
1772
+ })
1773
+ }),
1774
+ /* @__PURE__ */ a("div", {
1775
+ className: "sticker-admin-card__actions",
1776
+ children: /* @__PURE__ */ a("button", {
1777
+ className: "btn btn--danger",
1778
+ onClick: () => Y(e.filename),
1779
+ disabled: n || b || S,
1780
+ children: n ? "Deleting…" : "Delete"
1781
+ })
1782
+ })
1783
+ ]
1784
+ })]
1785
+ }, e.filename);
1786
+ })
1787
+ })
1788
+ ]
1789
+ });
1790
+ }
1791
+ //#endregion
1792
+ //#region src/components/ThemeAdminPage.tsx
1793
+ var ne = [
1794
+ {
1795
+ key: "--relaya-color-bg",
1796
+ label: "Chat background",
1797
+ hint: "Outermost container background",
1798
+ lightDefault: "#f0f2f5",
1799
+ darkDefault: "#0d1117"
1800
+ },
1801
+ {
1802
+ key: "--relaya-color-message-bg",
1803
+ label: "Others' message bubbles",
1804
+ hint: "Bubble background for received messages",
1805
+ lightDefault: "#e9ecef",
1806
+ darkDefault: "#21262d"
1807
+ },
1808
+ {
1809
+ key: "--relaya-color-message-own-bg",
1810
+ label: "Your message bubbles",
1811
+ hint: "Bubble background for sent messages",
1812
+ lightDefault: "#007aff",
1813
+ darkDefault: "#1f6feb"
1814
+ },
1815
+ {
1816
+ key: "--relaya-color-text",
1817
+ label: "Body text",
1818
+ hint: "Primary text colour",
1819
+ lightDefault: "#1a1a2e",
1820
+ darkDefault: "#e6edf3"
1821
+ },
1822
+ {
1823
+ key: "--relaya-color-text-secondary",
1824
+ label: "Secondary / muted text",
1825
+ hint: "Timestamps, labels",
1826
+ lightDefault: "#6c757d",
1827
+ darkDefault: "#8b949e"
1828
+ },
1829
+ {
1830
+ key: "--relaya-color-input-bg",
1831
+ label: "Message input background",
1832
+ hint: "Text-entry field background",
1833
+ lightDefault: "#f8f9fa",
1834
+ darkDefault: "#21262d"
1835
+ },
1836
+ {
1837
+ key: "--relaya-color-input-text",
1838
+ label: "Message input text",
1839
+ hint: "Text typed in the input",
1840
+ lightDefault: "#1a1a2e",
1841
+ darkDefault: "#e6edf3"
1842
+ },
1843
+ {
1844
+ key: "--relaya-color-btn-bg",
1845
+ label: "Send button background",
1846
+ hint: "Primary action button background",
1847
+ lightDefault: "#007aff",
1848
+ darkDefault: "#1f6feb"
1849
+ },
1850
+ {
1851
+ key: "--relaya-color-btn-text",
1852
+ label: "Send button icon",
1853
+ hint: "Icon colour on the send button",
1854
+ lightDefault: "#ffffff",
1855
+ darkDefault: "#ffffff"
1856
+ },
1857
+ {
1858
+ key: "--relaya-color-name-mod",
1859
+ label: "Moderator name colour",
1860
+ hint: "Display name colour for moderators",
1861
+ lightDefault: "#007aff",
1862
+ darkDefault: "#58a6ff"
1863
+ },
1864
+ {
1865
+ key: "--relaya-color-link",
1866
+ label: "Link colour",
1867
+ hint: "Hyperlink default state",
1868
+ lightDefault: "#007aff",
1869
+ darkDefault: "#58a6ff"
1870
+ },
1871
+ {
1872
+ key: "--relaya-color-link-active",
1873
+ label: "Link hover/active colour",
1874
+ hint: "Hyperlink hover/active state",
1875
+ lightDefault: "#0062cc",
1876
+ darkDefault: "#79b8ff"
1877
+ }
1878
+ ], re = {};
1879
+ function X(e) {
1880
+ return /^#[0-9a-fA-F]{6}$/.test(e.trim());
1881
+ }
1882
+ function ie(e) {
1883
+ let t = e.trim();
1884
+ return /^[0-9a-fA-F]{6}$/.test(t) ? `#${t}` : /^#[0-9a-fA-F]{3}$/.test(t) ? `#${t[1]}${t[1]}${t[2]}${t[2]}${t[3]}${t[3]}` : t;
1885
+ }
1886
+ function ae({ stationSlug: e, getToken: n, onClose: i }) {
1887
+ let { theme: s, loading: c, saving: l, error: d, loadTheme: f, saveTheme: p, resetTheme: m } = T(e, n), [h, g] = r(u.theme), [_, v] = r({
1888
+ light: {},
1889
+ dark: {}
1890
+ }), [y, b] = r(!1), [x, S] = r(null), [C, w] = r({});
1891
+ t(() => {
1892
+ let e = document.createElement("div");
1893
+ e.style.display = "none", document.documentElement.appendChild(e);
1894
+ let t = {};
1895
+ for (let { key: n } of ne) {
1896
+ e.style.setProperty("background-color", `var(${n})`);
1897
+ let r = getComputedStyle(e).backgroundColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
1898
+ r && (t[n] = "#" + [
1899
+ r[1],
1900
+ r[2],
1901
+ r[3]
1902
+ ].map((e) => parseInt(e).toString(16).padStart(2, "0")).join(""));
1903
+ }
1904
+ document.documentElement.removeChild(e), w(t);
1905
+ }, []), t(() => {
1906
+ f();
1907
+ }, []), t(() => {
1908
+ v({
1909
+ light: { ...s.light },
1910
+ dark: { ...s.dark }
1911
+ }), b(!1);
1912
+ }, [s]);
1913
+ function E(e, t) {
1914
+ let n = ie(t);
1915
+ v((t) => ({
1916
+ ...t,
1917
+ [h]: {
1918
+ ...t[h],
1919
+ [e]: n
1920
+ }
1921
+ })), b(!0), S(null);
1922
+ }
1923
+ async function D() {
1924
+ let e = {
1925
+ light: {},
1926
+ dark: {}
1927
+ };
1928
+ for (let t of ["light", "dark"]) for (let [n, r] of Object.entries(_[t])) r && X(r) && (e[t][n] = r);
1929
+ await p(e, h), b(!1), S("Theme saved.");
1930
+ }
1931
+ async function O() {
1932
+ confirm("Remove all saved colour overrides and revert to the default theme?") && (await m(h), b(!1), S("Theme reset to defaults."));
1933
+ }
1934
+ let k = _[h] ?? re;
1935
+ return /* @__PURE__ */ o("div", {
1936
+ className: "theme-admin-page",
1937
+ children: [
1938
+ /* @__PURE__ */ o("div", {
1939
+ className: "theme-admin-page__topbar",
1940
+ children: [/* @__PURE__ */ o("div", {
1941
+ className: "theme-admin-page__title-group",
1942
+ children: [/* @__PURE__ */ o("div", {
1943
+ className: "theme-admin-page__title-row",
1944
+ children: [
1945
+ i && /* @__PURE__ */ a("button", {
1946
+ className: "btn btn--ghost theme-admin-page__back",
1947
+ onClick: i,
1948
+ children: "← Back"
1949
+ }),
1950
+ /* @__PURE__ */ a("h2", {
1951
+ className: "theme-admin-page__title",
1952
+ children: "Colour theme"
1953
+ }),
1954
+ x && /* @__PURE__ */ a("div", {
1955
+ className: "theme-admin-page__status-inline",
1956
+ children: x
1957
+ })
1958
+ ]
1959
+ }), /* @__PURE__ */ a("p", {
1960
+ className: "theme-admin-page__subtitle",
1961
+ children: "Override the default colours for this space. Only saved values are stored; unset fields fall back to the default theme."
1962
+ })]
1963
+ }), /* @__PURE__ */ o("div", {
1964
+ className: "theme-admin-page__header-actions",
1965
+ children: [/* @__PURE__ */ a("button", {
1966
+ className: "btn btn--ghost",
1967
+ onClick: O,
1968
+ disabled: l || c,
1969
+ children: "Reset to defaults"
1970
+ }), /* @__PURE__ */ a("button", {
1971
+ className: "btn btn--primary",
1972
+ onClick: D,
1973
+ disabled: l || c || !y,
1974
+ children: l ? "Saving…" : "Save colours"
1975
+ })]
1976
+ })]
1977
+ }),
1978
+ d && /* @__PURE__ */ a("div", {
1979
+ className: "theme-admin-page__error",
1980
+ children: d
1981
+ }),
1982
+ /* @__PURE__ */ o("div", {
1983
+ className: "theme-admin-page__tabs",
1984
+ children: [/* @__PURE__ */ a("button", {
1985
+ className: `theme-admin-page__tab${h === "light" ? " theme-admin-page__tab--active" : ""}`,
1986
+ onClick: () => g("light"),
1987
+ children: "☀ Light"
1988
+ }), /* @__PURE__ */ a("button", {
1989
+ className: `theme-admin-page__tab${h === "dark" ? " theme-admin-page__tab--active" : ""}`,
1990
+ onClick: () => g("dark"),
1991
+ children: "☾ Dark"
1992
+ })]
1993
+ }),
1994
+ c ? /* @__PURE__ */ a("div", {
1995
+ className: "theme-admin-page__state",
1996
+ children: "Loading theme…"
1997
+ }) : /* @__PURE__ */ a("div", {
1998
+ className: "theme-admin-list",
1999
+ children: ne.map(({ key: e, label: t, hint: n, lightDefault: r, darkDefault: i }) => {
2000
+ let s = h === u.theme ? C[e] ?? (h === "light" ? r : i) : h === "light" ? r : i, c = k[e] ?? "", l = c ? ie(c) : s, d = c !== "" && !X(c);
2001
+ return /* @__PURE__ */ o("div", {
2002
+ className: "theme-admin-row",
2003
+ title: n,
2004
+ children: [
2005
+ /* @__PURE__ */ a("input", {
2006
+ type: "color",
2007
+ className: "theme-admin-row__swatch",
2008
+ value: X(l) ? l : s,
2009
+ onChange: (t) => E(e, t.target.value),
2010
+ title: t
2011
+ }),
2012
+ /* @__PURE__ */ a("label", {
2013
+ className: "theme-admin-row__label",
2014
+ children: t
2015
+ }),
2016
+ /* @__PURE__ */ a("input", {
2017
+ type: "text",
2018
+ className: `theme-admin-row__hex${d ? " theme-admin-row__hex--invalid" : ""}`,
2019
+ value: c,
2020
+ placeholder: s,
2021
+ maxLength: 9,
2022
+ onChange: (t) => E(e, t.target.value)
2023
+ })
2024
+ ]
2025
+ }, e);
2026
+ })
2027
+ })
2028
+ ]
2029
+ });
2030
+ }
2031
+ //#endregion
2032
+ //#region src/components/ExportAdminPage.tsx
2033
+ function Z(e) {
2034
+ return e.toISOString().slice(0, 10);
2035
+ }
2036
+ function oe() {
2037
+ let e = /* @__PURE__ */ new Date();
2038
+ return {
2039
+ from: Z(/* @__PURE__ */ new Date(e.getTime() - 90 * 864e5)),
2040
+ to: Z(e),
2041
+ excludeReportedBefore: Z(/* @__PURE__ */ new Date(e.getTime() - 365 * 864e5))
2042
+ };
2043
+ }
2044
+ var se = "A date is required — reported messages are kept indefinitely for compliance. Choose a date to limit how far back they appear in this export.";
2045
+ function ce({ stationSlug: e, getToken: t }) {
2046
+ let n = oe(), [i, s] = r(n.from), [c, l] = r(n.to), [u, d] = r(n.excludeReportedBefore), [f, p] = r(null), [m, h] = r(!1), [g, _] = r(null), [v, y] = r(!1);
2047
+ function b() {
2048
+ p(u ? null : se);
2049
+ }
2050
+ async function x() {
2051
+ if (!u) {
2052
+ p(se);
2053
+ return;
2054
+ }
2055
+ p(null), _(null), h(!0);
2056
+ try {
2057
+ let n = new URLSearchParams();
2058
+ i && n.set("from", i + "T00:00:00Z"), c && n.set("to", c + "T23:59:59Z"), n.set("excludeReportedBefore", u + "T00:00:00Z");
2059
+ let r = `/api/chat/${e}/export/messages?${n.toString()}`, a = t(), o = {};
2060
+ a && (o.Authorization = `Bearer ${a}`);
2061
+ let s = await fetch(r, {
2062
+ credentials: "include",
2063
+ headers: o
2064
+ });
2065
+ if (s.ok) {
2066
+ let t = await s.blob(), n = (s.headers.get("Content-Disposition") ?? "").match(/filename="([^"]+)"/), r = n ? n[1] : `${e}-export.csv`, i = document.createElement("a");
2067
+ i.href = URL.createObjectURL(t), i.download = r, document.body.appendChild(i), i.click(), i.remove(), URL.revokeObjectURL(i.href);
2068
+ return;
2069
+ }
2070
+ let l = {};
2071
+ try {
2072
+ l = await s.json();
2073
+ } catch {}
2074
+ if (s.status === 403) {
2075
+ let e = l.error;
2076
+ if (typeof e == "object" && e?.code === "TIER_LIMIT") {
2077
+ y(!0);
2078
+ return;
2079
+ }
2080
+ _("You do not have permission to export chat history for this space.");
2081
+ return;
2082
+ }
2083
+ if (s.status === 429) {
2084
+ _(`Export rate limit reached — you can export once per hour. Try again in ${Math.ceil((l.retryAfter ?? 3600) / 60)} minutes.`);
2085
+ return;
2086
+ }
2087
+ if (s.status === 413) {
2088
+ _("Too many messages in this date range. Please narrow the date range and try again.");
2089
+ return;
2090
+ }
2091
+ _((typeof l.error == "object" ? l.error?.message : typeof l.error == "string" ? l.error : void 0) ?? "Export failed. Please try again.");
2092
+ } catch {
2093
+ _("Export failed due to a network error. Please try again.");
2094
+ } finally {
2095
+ h(!1);
2096
+ }
2097
+ }
2098
+ return v ? /* @__PURE__ */ a("div", {
2099
+ className: "export-admin-page",
2100
+ children: /* @__PURE__ */ a("p", {
2101
+ className: "export-admin-page__unavailable",
2102
+ children: "Chat history export is not available on your current plan."
2103
+ })
2104
+ }) : /* @__PURE__ */ o("div", {
2105
+ className: "export-admin-page",
2106
+ children: [
2107
+ /* @__PURE__ */ a("p", {
2108
+ className: "export-admin-page__notice",
2109
+ children: "Exports contain user display names and message content. Handle in accordance with your privacy policy."
2110
+ }),
2111
+ g && /* @__PURE__ */ a("div", {
2112
+ className: "export-admin-page__error",
2113
+ children: g
2114
+ }),
2115
+ /* @__PURE__ */ o("div", {
2116
+ className: "export-admin-page__form",
2117
+ children: [
2118
+ /* @__PURE__ */ o("div", {
2119
+ className: "export-admin-page__date-row",
2120
+ children: [/* @__PURE__ */ o("label", {
2121
+ className: "export-admin-page__label",
2122
+ children: ["From:", /* @__PURE__ */ a("input", {
2123
+ type: "date",
2124
+ className: "export-admin-page__date-input",
2125
+ value: i,
2126
+ onChange: (e) => s(e.target.value)
2127
+ })]
2128
+ }), /* @__PURE__ */ o("label", {
2129
+ className: "export-admin-page__label",
2130
+ children: ["To:", /* @__PURE__ */ a("input", {
2131
+ type: "date",
2132
+ className: "export-admin-page__date-input",
2133
+ value: c,
2134
+ onChange: (e) => l(e.target.value)
2135
+ })]
2136
+ })]
2137
+ }),
2138
+ /* @__PURE__ */ o("div", {
2139
+ className: "export-admin-page__exclude-row",
2140
+ children: [/* @__PURE__ */ o("label", {
2141
+ className: "export-admin-page__label",
2142
+ children: ["Exclude reported messages older than:", /* @__PURE__ */ a("input", {
2143
+ type: "date",
2144
+ className: "export-admin-page__date-input",
2145
+ value: u,
2146
+ onChange: (e) => {
2147
+ d(e.target.value), p(null);
2148
+ },
2149
+ onBlur: b,
2150
+ required: !0
2151
+ })]
2152
+ }), f && /* @__PURE__ */ a("p", {
2153
+ className: "export-admin-page__field-error",
2154
+ children: f
2155
+ })]
2156
+ }),
2157
+ /* @__PURE__ */ a("button", {
2158
+ className: "btn btn--primary export-admin-page__btn",
2159
+ onClick: x,
2160
+ disabled: m,
2161
+ children: m ? "Preparing…" : "Download CSV →"
2162
+ }),
2163
+ /* @__PURE__ */ a("p", {
2164
+ className: "export-admin-page__hint",
2165
+ children: "Export is limited to the space’s retention window. Rate limited: 1 export per hour."
2166
+ })
2167
+ ]
2168
+ })
2169
+ ]
2170
+ });
2171
+ }
2172
+ //#endregion
2173
+ //#region src/components/countryNames.ts
2174
+ var Q = {
2175
+ AF: "Afghanistan",
2176
+ AX: "Åland Islands",
2177
+ AL: "Albania",
2178
+ DZ: "Algeria",
2179
+ AS: "American Samoa",
2180
+ AD: "Andorra",
2181
+ AO: "Angola",
2182
+ AI: "Anguilla",
2183
+ AQ: "Antarctica",
2184
+ AG: "Antigua and Barbuda",
2185
+ AR: "Argentina",
2186
+ AM: "Armenia",
2187
+ AW: "Aruba",
2188
+ AU: "Australia",
2189
+ AT: "Austria",
2190
+ AZ: "Azerbaijan",
2191
+ BS: "Bahamas",
2192
+ BH: "Bahrain",
2193
+ BD: "Bangladesh",
2194
+ BB: "Barbados",
2195
+ BY: "Belarus",
2196
+ BE: "Belgium",
2197
+ BZ: "Belize",
2198
+ BJ: "Benin",
2199
+ BM: "Bermuda",
2200
+ BT: "Bhutan",
2201
+ BO: "Bolivia",
2202
+ BQ: "Bonaire, Sint Eustatius and Saba",
2203
+ BA: "Bosnia and Herzegovina",
2204
+ BW: "Botswana",
2205
+ BV: "Bouvet Island",
2206
+ BR: "Brazil",
2207
+ IO: "British Indian Ocean Territory",
2208
+ BN: "Brunei",
2209
+ BG: "Bulgaria",
2210
+ BF: "Burkina Faso",
2211
+ BI: "Burundi",
2212
+ CV: "Cabo Verde",
2213
+ KH: "Cambodia",
2214
+ CM: "Cameroon",
2215
+ CA: "Canada",
2216
+ KY: "Cayman Islands",
2217
+ CF: "Central African Republic",
2218
+ TD: "Chad",
2219
+ CL: "Chile",
2220
+ CN: "China",
2221
+ CX: "Christmas Island",
2222
+ CC: "Cocos Islands",
2223
+ CO: "Colombia",
2224
+ KM: "Comoros",
2225
+ CG: "Congo",
2226
+ CD: "Congo (DRC)",
2227
+ CK: "Cook Islands",
2228
+ CR: "Costa Rica",
2229
+ CI: "Côte d'Ivoire",
2230
+ HR: "Croatia",
2231
+ CU: "Cuba",
2232
+ CW: "Curaçao",
2233
+ CY: "Cyprus",
2234
+ CZ: "Czechia",
2235
+ DK: "Denmark",
2236
+ DJ: "Djibouti",
2237
+ DM: "Dominica",
2238
+ DO: "Dominican Republic",
2239
+ EC: "Ecuador",
2240
+ EG: "Egypt",
2241
+ SV: "El Salvador",
2242
+ GQ: "Equatorial Guinea",
2243
+ ER: "Eritrea",
2244
+ EE: "Estonia",
2245
+ SZ: "Eswatini",
2246
+ ET: "Ethiopia",
2247
+ FK: "Falkland Islands",
2248
+ FO: "Faroe Islands",
2249
+ FJ: "Fiji",
2250
+ FI: "Finland",
2251
+ FR: "France",
2252
+ GF: "French Guiana",
2253
+ PF: "French Polynesia",
2254
+ TF: "French Southern Territories",
2255
+ GA: "Gabon",
2256
+ GM: "Gambia",
2257
+ GE: "Georgia",
2258
+ DE: "Germany",
2259
+ GH: "Ghana",
2260
+ GI: "Gibraltar",
2261
+ GR: "Greece",
2262
+ GL: "Greenland",
2263
+ GD: "Grenada",
2264
+ GP: "Guadeloupe",
2265
+ GU: "Guam",
2266
+ GT: "Guatemala",
2267
+ GG: "Guernsey",
2268
+ GN: "Guinea",
2269
+ GW: "Guinea-Bissau",
2270
+ GY: "Guyana",
2271
+ HT: "Haiti",
2272
+ HM: "Heard Island and McDonald Islands",
2273
+ VA: "Holy See",
2274
+ HN: "Honduras",
2275
+ HK: "Hong Kong",
2276
+ HU: "Hungary",
2277
+ IS: "Iceland",
2278
+ IN: "India",
2279
+ ID: "Indonesia",
2280
+ IR: "Iran",
2281
+ IQ: "Iraq",
2282
+ IE: "Ireland",
2283
+ IM: "Isle of Man",
2284
+ IL: "Israel",
2285
+ IT: "Italy",
2286
+ JM: "Jamaica",
2287
+ JP: "Japan",
2288
+ JE: "Jersey",
2289
+ JO: "Jordan",
2290
+ KZ: "Kazakhstan",
2291
+ KE: "Kenya",
2292
+ KI: "Kiribati",
2293
+ KP: "North Korea",
2294
+ KR: "South Korea",
2295
+ KW: "Kuwait",
2296
+ KG: "Kyrgyzstan",
2297
+ LA: "Laos",
2298
+ LV: "Latvia",
2299
+ LB: "Lebanon",
2300
+ LS: "Lesotho",
2301
+ LR: "Liberia",
2302
+ LY: "Libya",
2303
+ LI: "Liechtenstein",
2304
+ LT: "Lithuania",
2305
+ LU: "Luxembourg",
2306
+ MO: "Macao",
2307
+ MG: "Madagascar",
2308
+ MW: "Malawi",
2309
+ MY: "Malaysia",
2310
+ MV: "Maldives",
2311
+ ML: "Mali",
2312
+ MT: "Malta",
2313
+ MH: "Marshall Islands",
2314
+ MQ: "Martinique",
2315
+ MR: "Mauritania",
2316
+ MU: "Mauritius",
2317
+ YT: "Mayotte",
2318
+ MX: "Mexico",
2319
+ FM: "Micronesia",
2320
+ MD: "Moldova",
2321
+ MC: "Monaco",
2322
+ MN: "Mongolia",
2323
+ ME: "Montenegro",
2324
+ MS: "Montserrat",
2325
+ MA: "Morocco",
2326
+ MZ: "Mozambique",
2327
+ MM: "Myanmar",
2328
+ NA: "Namibia",
2329
+ NR: "Nauru",
2330
+ NP: "Nepal",
2331
+ NL: "Netherlands",
2332
+ NC: "New Caledonia",
2333
+ NZ: "New Zealand",
2334
+ NI: "Nicaragua",
2335
+ NE: "Niger",
2336
+ NG: "Nigeria",
2337
+ NU: "Niue",
2338
+ NF: "Norfolk Island",
2339
+ MK: "North Macedonia",
2340
+ MP: "Northern Mariana Islands",
2341
+ NO: "Norway",
2342
+ OM: "Oman",
2343
+ PK: "Pakistan",
2344
+ PW: "Palau",
2345
+ PS: "Palestine",
2346
+ PA: "Panama",
2347
+ PG: "Papua New Guinea",
2348
+ PY: "Paraguay",
2349
+ PE: "Peru",
2350
+ PH: "Philippines",
2351
+ PN: "Pitcairn",
2352
+ PL: "Poland",
2353
+ PT: "Portugal",
2354
+ PR: "Puerto Rico",
2355
+ QA: "Qatar",
2356
+ RE: "Réunion",
2357
+ RO: "Romania",
2358
+ RU: "Russia",
2359
+ RW: "Rwanda",
2360
+ BL: "Saint Barthélemy",
2361
+ SH: "Saint Helena",
2362
+ KN: "Saint Kitts and Nevis",
2363
+ LC: "Saint Lucia",
2364
+ MF: "Saint Martin",
2365
+ PM: "Saint Pierre and Miquelon",
2366
+ VC: "Saint Vincent and the Grenadines",
2367
+ WS: "Samoa",
2368
+ SM: "San Marino",
2369
+ ST: "Sao Tome and Principe",
2370
+ SA: "Saudi Arabia",
2371
+ SN: "Senegal",
2372
+ RS: "Serbia",
2373
+ SC: "Seychelles",
2374
+ SL: "Sierra Leone",
2375
+ SG: "Singapore",
2376
+ SX: "Sint Maarten",
2377
+ SK: "Slovakia",
2378
+ SI: "Slovenia",
2379
+ SB: "Solomon Islands",
2380
+ SO: "Somalia",
2381
+ ZA: "South Africa",
2382
+ GS: "South Georgia",
2383
+ SS: "South Sudan",
2384
+ ES: "Spain",
2385
+ LK: "Sri Lanka",
2386
+ SD: "Sudan",
2387
+ SR: "Suriname",
2388
+ SJ: "Svalbard and Jan Mayen",
2389
+ SE: "Sweden",
2390
+ CH: "Switzerland",
2391
+ SY: "Syria",
2392
+ TW: "Taiwan",
2393
+ TJ: "Tajikistan",
2394
+ TZ: "Tanzania",
2395
+ TH: "Thailand",
2396
+ TL: "Timor-Leste",
2397
+ TG: "Togo",
2398
+ TK: "Tokelau",
2399
+ TO: "Tonga",
2400
+ TT: "Trinidad and Tobago",
2401
+ TN: "Tunisia",
2402
+ TR: "Turkey",
2403
+ TM: "Turkmenistan",
2404
+ TC: "Turks and Caicos Islands",
2405
+ TV: "Tuvalu",
2406
+ UG: "Uganda",
2407
+ UA: "Ukraine",
2408
+ AE: "United Arab Emirates",
2409
+ GB: "United Kingdom",
2410
+ US: "United States",
2411
+ UM: "US Minor Outlying Islands",
2412
+ UY: "Uruguay",
2413
+ UZ: "Uzbekistan",
2414
+ VU: "Vanuatu",
2415
+ VE: "Venezuela",
2416
+ VN: "Vietnam",
2417
+ VG: "British Virgin Islands",
2418
+ VI: "US Virgin Islands",
2419
+ WF: "Wallis and Futuna",
2420
+ EH: "Western Sahara",
2421
+ YE: "Yemen",
2422
+ ZM: "Zambia",
2423
+ ZW: "Zimbabwe"
2424
+ };
2425
+ function le(e) {
2426
+ let t = Q[e.toUpperCase()];
2427
+ return t ? `${t} (${e.toUpperCase()})` : e.toUpperCase();
2428
+ }
2429
+ function ue(e) {
2430
+ let t = Q[e.toUpperCase()];
2431
+ return t ? `${t} \u00b7 ${e.toUpperCase()}` : e.toUpperCase();
2432
+ }
2433
+ var de = Object.entries(Q).map(([e, t]) => ({
2434
+ code: e,
2435
+ name: t
2436
+ })).sort((e, t) => e.name.localeCompare(t.name));
2437
+ //#endregion
2438
+ //#region src/components/GeoRestrictionsAdmin.tsx
2439
+ function $(e) {
2440
+ let t = e();
2441
+ return t ? { Authorization: `Bearer ${t}` } : {};
2442
+ }
2443
+ function fe(e) {
2444
+ if (e.isPermanent || !e.expiresAt) return "Permanent";
2445
+ let t = new Date(e.expiresAt);
2446
+ return t < /* @__PURE__ */ new Date() ? "Expired" : `Until ${t.toLocaleDateString(void 0, {
2447
+ month: "short",
2448
+ day: "numeric",
2449
+ year: "numeric"
2450
+ })}`;
2451
+ }
2452
+ function pe({ stationSlug: e, getToken: s }) {
2453
+ let c = `/api/chat/${e}`, [l, u] = r(!1), [d, f] = r(!1), [p, m] = r(null), [h, g] = r({
2454
+ mode: null,
2455
+ countries: []
2456
+ }), [_, v] = r([]), [y, b] = r(""), [x, S] = r(!1), C = n(null), [w, T] = r(""), [E, D] = r(!1), [O, k] = r(""), [A, j] = r(""), [M, N] = r(""), [P, F] = r(!1), [I, L] = r(null);
2457
+ t(() => {
2458
+ (async () => {
2459
+ u(!0), m(null);
2460
+ try {
2461
+ let e = {
2462
+ ...$(s),
2463
+ "Content-Type": "application/json"
2464
+ }, [t, n] = await Promise.all([fetch(`${c}/geo/config`, {
2465
+ credentials: "include",
2466
+ headers: e
2467
+ }), fetch(`${c}/ip-bans`, {
2468
+ credentials: "include",
2469
+ headers: e
2470
+ })]);
2471
+ if (t.status === 403) {
2472
+ let e = {};
2473
+ try {
2474
+ e = await t.json();
2475
+ } catch {}
2476
+ if (typeof e.error == "object" && e.error?.code === "TIER_LIMIT") {
2477
+ f(!0), u(!1);
2478
+ return;
2479
+ }
2480
+ }
2481
+ if (!t.ok) throw Error("Failed to load geo config");
2482
+ if (!n.ok) throw Error("Failed to load IP bans");
2483
+ let r = await t.json(), i = await n.json();
2484
+ g({
2485
+ mode: r.mode ?? null,
2486
+ countries: r.countries ?? []
2487
+ }), v(i.bans ?? []);
2488
+ } catch (e) {
2489
+ m(e?.message ?? "Failed to load geo restriction settings");
2490
+ } finally {
2491
+ u(!1);
2492
+ }
2493
+ })();
2494
+ }, []), t(() => {
2495
+ function e(e) {
2496
+ C.current && !C.current.contains(e.target) && S(!1);
2497
+ }
2498
+ return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
2499
+ }, []);
2500
+ async function R(e, t) {
2501
+ let n = await fetch(`${c}${e}`, {
2502
+ method: "POST",
2503
+ credentials: "include",
2504
+ headers: {
2505
+ ...$(s),
2506
+ "Content-Type": "application/json"
2507
+ },
2508
+ body: JSON.stringify(t)
2509
+ });
2510
+ if (!n.ok) {
2511
+ let e = "Request failed";
2512
+ try {
2513
+ let t = await n.json();
2514
+ e = t?.error?.message ?? t?.error ?? e;
2515
+ } catch {}
2516
+ throw Error(typeof e == "string" ? e : "Request failed");
2517
+ }
2518
+ return n.json();
2519
+ }
2520
+ async function z(e) {
2521
+ let t = await fetch(`${c}${e}`, {
2522
+ method: "DELETE",
2523
+ credentials: "include",
2524
+ headers: $(s)
2525
+ });
2526
+ if (!t.ok) {
2527
+ let e = "Request failed";
2528
+ try {
2529
+ let n = await t.json();
2530
+ e = n?.error?.message ?? n?.error ?? e;
2531
+ } catch {}
2532
+ throw Error(typeof e == "string" ? e : "Request failed");
2533
+ }
2534
+ return t.json();
2535
+ }
2536
+ async function B(e) {
2537
+ L(null);
2538
+ let t = e === "none" ? null : e;
2539
+ try {
2540
+ let e = await R("/geo/config", { mode: t });
2541
+ g({
2542
+ mode: e.mode ?? null,
2543
+ countries: e.countries ?? []
2544
+ });
2545
+ } catch (e) {
2546
+ L(e?.message ?? "Failed to update mode");
2547
+ }
2548
+ }
2549
+ async function V(e) {
2550
+ L(null), b(""), S(!1);
2551
+ try {
2552
+ let t = await R("/geo/countries", { countryCode: e });
2553
+ g({
2554
+ mode: t.mode ?? null,
2555
+ countries: t.countries ?? []
2556
+ });
2557
+ } catch (e) {
2558
+ L(e?.message ?? "Failed to add country");
2559
+ }
2560
+ }
2561
+ async function H(e) {
2562
+ L(null);
2563
+ try {
2564
+ let t = await z(`/geo/countries/${e}`);
2565
+ g({
2566
+ mode: t.mode ?? null,
2567
+ countries: t.countries ?? []
2568
+ });
2569
+ } catch (e) {
2570
+ L(e?.message ?? "Failed to remove country");
2571
+ }
2572
+ }
2573
+ async function U(e) {
2574
+ L(null);
2575
+ try {
2576
+ await z(`/ip-bans/${e}`), v((t) => t.filter((t) => t.id !== e));
2577
+ } catch (e) {
2578
+ L(e?.message ?? "Failed to lift ban");
2579
+ }
2580
+ }
2581
+ async function W(e) {
2582
+ if (e.preventDefault(), O.trim()) {
2583
+ L(null), F(!0);
2584
+ try {
2585
+ let e = { ipAddress: O.trim() };
2586
+ if (A.trim() && (e.reason = A.trim()), M.trim()) {
2587
+ let t = parseInt(M, 10);
2588
+ !isNaN(t) && t > 0 && (e.durationMinutes = t);
2589
+ }
2590
+ let t = await R("/ip-bans", e);
2591
+ v((e) => [{
2592
+ id: t.id,
2593
+ ipAddress: t.ipAddress,
2594
+ reason: t.reason ?? null,
2595
+ expiresAt: t.expiresAt ?? null,
2596
+ isPermanent: t.isPermanent,
2597
+ createdAt: t.createdAt
2598
+ }, ...e]), k(""), j(""), N(""), D(!1);
2599
+ } catch (e) {
2600
+ L(e?.message ?? "Failed to add IP ban");
2601
+ } finally {
2602
+ F(!1);
2603
+ }
2604
+ }
2605
+ }
2606
+ if (l) return /* @__PURE__ */ a("div", {
2607
+ className: "geo-admin__status",
2608
+ children: "Loading…"
2609
+ });
2610
+ if (d) return /* @__PURE__ */ a("div", {
2611
+ className: "geo-admin__status geo-admin__status--unavailable",
2612
+ children: "Geo restrictions and IP bans are available on the Community plan and above."
2613
+ });
2614
+ if (p) return /* @__PURE__ */ a("div", {
2615
+ className: "geo-admin__status geo-admin__status--error",
2616
+ children: p
2617
+ });
2618
+ let G = y.trim().toLowerCase(), K = (G ? de.filter((e) => e.name.toLowerCase().includes(G) || e.code.toLowerCase().includes(G)).slice(0, 12) : de.slice(0, 12)).filter((e) => !h.countries.includes(e.code)), q = w.trim() ? _.filter((e) => e.ipAddress.includes(w.trim()) || (e.reason ?? "").toLowerCase().includes(w.trim().toLowerCase())) : _;
2619
+ return /* @__PURE__ */ o("div", {
2620
+ className: "geo-admin",
2621
+ children: [
2622
+ I && /* @__PURE__ */ a("div", {
2623
+ className: "geo-admin__action-error",
2624
+ children: I
2625
+ }),
2626
+ /* @__PURE__ */ o("div", {
2627
+ className: "geo-admin__section",
2628
+ children: [
2629
+ /* @__PURE__ */ a("h4", {
2630
+ className: "geo-admin__section-title",
2631
+ children: "Country Restrictions"
2632
+ }),
2633
+ /* @__PURE__ */ o("div", {
2634
+ className: "geo-admin__field",
2635
+ children: [/* @__PURE__ */ a("label", {
2636
+ className: "geo-admin__label",
2637
+ children: "Restriction mode"
2638
+ }), /* @__PURE__ */ o("select", {
2639
+ className: "geo-admin__select",
2640
+ value: h.mode ?? "none",
2641
+ onChange: (e) => B(e.target.value),
2642
+ children: [
2643
+ /* @__PURE__ */ a("option", {
2644
+ value: "none",
2645
+ children: "None (unrestricted)"
2646
+ }),
2647
+ /* @__PURE__ */ a("option", {
2648
+ value: "blocklist",
2649
+ children: "Blocklist — block listed countries"
2650
+ }),
2651
+ /* @__PURE__ */ a("option", {
2652
+ value: "allowlist",
2653
+ children: "Allowlist — only listed countries"
2654
+ })
2655
+ ]
2656
+ })]
2657
+ }),
2658
+ h.mode && /* @__PURE__ */ o(i, { children: [
2659
+ /* @__PURE__ */ a("p", {
2660
+ className: "geo-admin__hint",
2661
+ children: h.mode === "blocklist" ? "Users from these countries will be blocked from posting." : "Only users from these countries can post."
2662
+ }),
2663
+ h.countries.length > 0 ? /* @__PURE__ */ a("div", {
2664
+ className: "geo-admin__chips",
2665
+ children: h.countries.map((e) => /* @__PURE__ */ o("span", {
2666
+ className: "geo-admin__chip",
2667
+ children: [ue(e), /* @__PURE__ */ a("button", {
2668
+ className: "geo-admin__chip-remove",
2669
+ onClick: () => H(e),
2670
+ title: `Remove ${e}`,
2671
+ "aria-label": `Remove ${e}`,
2672
+ children: "×"
2673
+ })]
2674
+ }, e))
2675
+ }) : /* @__PURE__ */ a("p", {
2676
+ className: "geo-admin__hint geo-admin__hint--empty",
2677
+ children: "No countries in list yet."
2678
+ }),
2679
+ /* @__PURE__ */ o("div", {
2680
+ className: "geo-admin__combobox",
2681
+ ref: C,
2682
+ children: [
2683
+ /* @__PURE__ */ a("input", {
2684
+ className: "geo-admin__combobox-input",
2685
+ type: "text",
2686
+ placeholder: "Search country to add…",
2687
+ value: y,
2688
+ onChange: (e) => {
2689
+ b(e.target.value), S(!0);
2690
+ },
2691
+ onFocus: () => S(!0),
2692
+ onKeyDown: (e) => {
2693
+ e.key === "Escape" && (S(!1), b("")), e.key === "Enter" && K.length === 1 && V(K[0].code);
2694
+ }
2695
+ }),
2696
+ x && K.length > 0 && /* @__PURE__ */ a("ul", {
2697
+ className: "geo-admin__combobox-list",
2698
+ children: K.map((e) => /* @__PURE__ */ a("li", { children: /* @__PURE__ */ a("button", {
2699
+ className: "geo-admin__combobox-item",
2700
+ onMouseDown: (t) => {
2701
+ t.preventDefault(), V(e.code);
2702
+ },
2703
+ children: le(e.code)
2704
+ }) }, e.code))
2705
+ }),
2706
+ x && y.trim() && K.length === 0 && /* @__PURE__ */ a("div", {
2707
+ className: "geo-admin__combobox-empty",
2708
+ children: "No matching countries"
2709
+ })
2710
+ ]
2711
+ })
2712
+ ] })
2713
+ ]
2714
+ }),
2715
+ /* @__PURE__ */ o("div", {
2716
+ className: "geo-admin__section",
2717
+ children: [
2718
+ /* @__PURE__ */ o("h4", {
2719
+ className: "geo-admin__section-title",
2720
+ children: ["IP Bans", _.length > 0 && /* @__PURE__ */ a("span", {
2721
+ className: "geo-admin__badge",
2722
+ children: _.length
2723
+ })]
2724
+ }),
2725
+ _.length > 4 && /* @__PURE__ */ a("input", {
2726
+ className: "geo-admin__filter-input",
2727
+ type: "text",
2728
+ placeholder: "Filter by IP or reason…",
2729
+ value: w,
2730
+ onChange: (e) => T(e.target.value)
2731
+ }),
2732
+ q.length === 0 && _.length === 0 ? /* @__PURE__ */ a("p", {
2733
+ className: "geo-admin__hint",
2734
+ children: "No active IP bans."
2735
+ }) : q.length === 0 ? /* @__PURE__ */ a("p", {
2736
+ className: "geo-admin__hint",
2737
+ children: "No bans match your filter."
2738
+ }) : /* @__PURE__ */ a("div", {
2739
+ className: "geo-admin__ban-list",
2740
+ children: q.map((e) => /* @__PURE__ */ o("div", {
2741
+ className: "geo-admin__ban-row",
2742
+ children: [/* @__PURE__ */ o("div", {
2743
+ className: "geo-admin__ban-info",
2744
+ children: [
2745
+ /* @__PURE__ */ a("span", {
2746
+ className: "geo-admin__ban-ip",
2747
+ children: e.ipAddress
2748
+ }),
2749
+ e.reason && /* @__PURE__ */ a("span", {
2750
+ className: "geo-admin__ban-reason",
2751
+ children: e.reason
2752
+ }),
2753
+ /* @__PURE__ */ a("span", {
2754
+ className: "geo-admin__ban-expiry",
2755
+ children: fe(e)
2756
+ })
2757
+ ]
2758
+ }), /* @__PURE__ */ a("button", {
2759
+ className: "btn btn--ghost geo-admin__lift-btn",
2760
+ onClick: () => U(e.id),
2761
+ children: "Lift ban"
2762
+ })]
2763
+ }, e.id))
2764
+ }),
2765
+ /* @__PURE__ */ o("button", {
2766
+ className: "geo-admin__add-toggle",
2767
+ onClick: () => D((e) => !e),
2768
+ children: [E ? "▼" : "▶", " Add IP ban"]
2769
+ }),
2770
+ E && /* @__PURE__ */ o("form", {
2771
+ className: "geo-admin__add-ban-form",
2772
+ onSubmit: W,
2773
+ children: [
2774
+ /* @__PURE__ */ o("div", {
2775
+ className: "geo-admin__field",
2776
+ children: [/* @__PURE__ */ a("label", {
2777
+ className: "geo-admin__label",
2778
+ children: "IP address or CIDR range *"
2779
+ }), /* @__PURE__ */ a("input", {
2780
+ className: "geo-admin__input",
2781
+ type: "text",
2782
+ placeholder: "e.g. 1.2.3.4 or 1.2.0.0/16",
2783
+ value: O,
2784
+ onChange: (e) => k(e.target.value),
2785
+ required: !0
2786
+ })]
2787
+ }),
2788
+ /* @__PURE__ */ o("div", {
2789
+ className: "geo-admin__field",
2790
+ children: [/* @__PURE__ */ a("label", {
2791
+ className: "geo-admin__label",
2792
+ children: "Reason (optional)"
2793
+ }), /* @__PURE__ */ a("input", {
2794
+ className: "geo-admin__input",
2795
+ type: "text",
2796
+ placeholder: "Reason for ban",
2797
+ value: A,
2798
+ onChange: (e) => j(e.target.value)
2799
+ })]
2800
+ }),
2801
+ /* @__PURE__ */ o("div", {
2802
+ className: "geo-admin__field",
2803
+ children: [/* @__PURE__ */ a("label", {
2804
+ className: "geo-admin__label",
2805
+ children: "Duration in minutes (blank = permanent)"
2806
+ }), /* @__PURE__ */ a("input", {
2807
+ className: "geo-admin__input",
2808
+ type: "number",
2809
+ min: "1",
2810
+ placeholder: "e.g. 1440 for 24 hours",
2811
+ value: M,
2812
+ onChange: (e) => N(e.target.value)
2813
+ })]
2814
+ }),
2815
+ /* @__PURE__ */ a("button", {
2816
+ className: "btn btn--primary",
2817
+ type: "submit",
2818
+ disabled: P || !O.trim(),
2819
+ children: P ? "Adding…" : "Add IP ban"
2820
+ })
2821
+ ]
2822
+ })
2823
+ ]
2824
+ })
2825
+ ]
2826
+ });
2827
+ }
2828
+ //#endregion
2829
+ //#region src/components/ModeratorAdminPage.tsx
2830
+ function me(e) {
2831
+ return e.email ? `${e.displayName} (${e.email})` : e.displayName;
2832
+ }
2833
+ function he(e, t) {
2834
+ return t === null ? `${e} moderator${e === 1 ? "" : "s"} — unlimited slots` : `${e} of ${t} moderator slot${t === 1 ? "" : "s"} used`;
2835
+ }
2836
+ function ge({ stationSlug: e, user: l, getToken: u }) {
2837
+ let d = n(new c("", u)).current, [f, p] = r([]), [m, h] = r(null), [g, _] = r(!0), [v, y] = r(null), [b, x] = r(""), [S, C] = r(null), [w, T] = r(null), E = l.permissions.includes(s.MANAGE_ROLES);
2838
+ async function D() {
2839
+ _(!0), y(null);
2840
+ try {
2841
+ let t = await d.getMembersAdmin(e);
2842
+ p(t.members), h(t.quota);
2843
+ } catch (e) {
2844
+ y(e?.message ?? "Failed to load moderators.");
2845
+ } finally {
2846
+ _(!1);
2847
+ }
2848
+ }
2849
+ if (t(() => {
2850
+ E && D().catch(() => void 0);
2851
+ }, [E, e]), !E) return null;
2852
+ let O = f.filter((e) => e.roles.includes("moderator")), k = f.filter((e) => !e.roles.includes("moderator") && !e.roles.includes("station_admin")), A = b.trim().toLowerCase(), j = A === "" ? k : k.filter((e) => e.displayName.toLowerCase().includes(A) || (e.email ?? "").toLowerCase().includes(A)), M = m !== null && m.limit !== null && m.used >= m.limit, N = S !== null || w !== null;
2853
+ async function P(t) {
2854
+ C(t), y(null);
2855
+ try {
2856
+ await d.patchMemberRoles(e, t, { add: ["moderator"] }), await D();
2857
+ } catch (e) {
2858
+ y(e?.message ?? "Failed to promote member.");
2859
+ } finally {
2860
+ C(null);
2861
+ }
2862
+ }
2863
+ async function F(t) {
2864
+ T(t), y(null);
2865
+ try {
2866
+ await d.patchMemberRoles(e, t, { remove: ["moderator"] }), await D();
2867
+ } catch (e) {
2868
+ y(e?.message ?? "Failed to demote moderator.");
2869
+ } finally {
2870
+ T(null);
2871
+ }
2872
+ }
2873
+ return /* @__PURE__ */ o("div", {
2874
+ className: "moderator-admin-page",
2875
+ children: [
2876
+ /* @__PURE__ */ a("div", {
2877
+ className: "moderator-admin-page__topbar",
2878
+ children: /* @__PURE__ */ o("div", {
2879
+ className: "moderator-admin-page__title-row",
2880
+ children: [/* @__PURE__ */ a("h2", {
2881
+ className: "moderator-admin-page__title",
2882
+ children: "Moderators"
2883
+ }), m && /* @__PURE__ */ a("span", {
2884
+ className: "sticker-admin-page__count",
2885
+ children: he(m.used, m.limit)
2886
+ })]
2887
+ })
2888
+ }),
2889
+ v && /* @__PURE__ */ a("div", {
2890
+ className: "sticker-admin-page__error",
2891
+ children: v
2892
+ }),
2893
+ g ? /* @__PURE__ */ a("div", {
2894
+ className: "sticker-admin-page__state",
2895
+ children: "Loading moderators…"
2896
+ }) : /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ o("div", {
2897
+ className: "moderator-admin-page__section",
2898
+ children: [
2899
+ /* @__PURE__ */ a("h3", {
2900
+ className: "moderator-admin-page__section-title",
2901
+ children: "Current moderators"
2902
+ }),
2903
+ /* @__PURE__ */ a("hr", { className: "moderator-admin-page__divider" }),
2904
+ O.length === 0 ? /* @__PURE__ */ a("div", {
2905
+ className: "sticker-admin-page__state",
2906
+ children: "No moderators assigned yet."
2907
+ }) : /* @__PURE__ */ a("ul", {
2908
+ className: "moderator-admin-page__list",
2909
+ children: O.map((e) => /* @__PURE__ */ o("li", {
2910
+ className: "moderator-admin-page__item",
2911
+ children: [/* @__PURE__ */ a("button", {
2912
+ className: "moderator-admin-page__demote-link",
2913
+ onClick: () => F(e.userId),
2914
+ disabled: N,
2915
+ children: w === e.userId ? "Demoting…" : "Demote"
2916
+ }), /* @__PURE__ */ a("span", {
2917
+ className: "moderator-admin-page__member-label",
2918
+ children: me(e)
2919
+ })]
2920
+ }, e.userId))
2921
+ })
2922
+ ]
2923
+ }), /* @__PURE__ */ o("div", {
2924
+ className: "moderator-admin-page__section",
2925
+ children: [
2926
+ /* @__PURE__ */ a("h3", {
2927
+ className: "moderator-admin-page__section-title",
2928
+ children: "Add a moderator"
2929
+ }),
2930
+ /* @__PURE__ */ a("hr", { className: "moderator-admin-page__divider" }),
2931
+ /* @__PURE__ */ a("input", {
2932
+ className: "moderator-admin-page__filter",
2933
+ type: "text",
2934
+ placeholder: "Filter by name or email",
2935
+ value: b,
2936
+ onChange: (e) => x(e.target.value)
2937
+ }),
2938
+ /* @__PURE__ */ a("p", {
2939
+ className: "moderator-admin-page__list-hint",
2940
+ children: "Recent users"
2941
+ }),
2942
+ j.length === 0 ? /* @__PURE__ */ a("div", {
2943
+ className: "sticker-admin-page__state",
2944
+ children: b ? "No members match that filter." : "No members available to promote."
2945
+ }) : /* @__PURE__ */ a("ul", {
2946
+ className: "moderator-admin-page__list",
2947
+ children: j.map((e) => /* @__PURE__ */ o("li", {
2948
+ className: "moderator-admin-page__item",
2949
+ children: [/* @__PURE__ */ a("button", {
2950
+ className: "moderator-admin-page__promote-link",
2951
+ onClick: () => P(e.userId),
2952
+ disabled: M || N,
2953
+ title: M ? "Moderator limit reached for your plan" : void 0,
2954
+ children: S === e.userId ? "Promoting…" : "Promote"
2955
+ }), /* @__PURE__ */ a("span", {
2956
+ className: "moderator-admin-page__member-label",
2957
+ children: me(e)
2958
+ })]
2959
+ }, e.userId))
2960
+ })
2961
+ ]
2962
+ })] })
2963
+ ]
2964
+ });
2965
+ }
2966
+ //#endregion
2967
+ //#region src/AdminPanel.tsx
2968
+ function _e({ className: e, spaceSlug: n, serverUrl: i = "", token: c, manageOwnRefreshToken: l, onSessionEnded: d }) {
2969
+ let f = l ?? !u.managed, p = x({
2970
+ spaceSlug: n,
2971
+ initialToken: c ?? null,
2972
+ manageOwnRefreshToken: f,
2973
+ onSessionEnded: d
2974
+ }), { user: m, stationSlug: h, getToken: g } = p;
2975
+ t(() => {
2976
+ if (!h) return;
2977
+ document.documentElement.setAttribute("data-theme", u.theme);
2978
+ let e = N(h);
2979
+ e && P(e);
2980
+ let t = `${i}/api/chat/${h}/theme`;
2981
+ fetch(t).then((e) => e.ok ? e.json() : {
2982
+ light: {},
2983
+ dark: {}
2984
+ }).then((e) => {
2985
+ (Object.keys(e?.light ?? {}).length > 0 || Object.keys(e?.dark ?? {}).length > 0) && w({
2986
+ light: e.light ?? {},
2987
+ dark: e.dark ?? {}
2988
+ }, u.theme);
2989
+ }).catch(() => {});
2990
+ }, [h, i]);
2991
+ let [_, v] = r(!1), [y, b] = r(!1), [S, C] = r(!1), [T, E] = r(!1), [D, O] = r(!1), k = e ? `relaya-root ${e}` : "relaya-root";
2992
+ if (p.status === "loading") return /* @__PURE__ */ a("div", {
2993
+ className: k,
2994
+ children: /* @__PURE__ */ a("div", {
2995
+ className: "admin-panel",
2996
+ children: /* @__PURE__ */ o("div", {
2997
+ className: "admin-panel__loading",
2998
+ children: [/* @__PURE__ */ a("div", { className: "connection-spinner" }), /* @__PURE__ */ a("span", { children: "Loading…" })]
2999
+ })
3000
+ })
3001
+ });
3002
+ if (p.status !== "authenticated" || !m) return /* @__PURE__ */ a("div", {
3003
+ className: k,
3004
+ children: /* @__PURE__ */ a("div", {
3005
+ className: "admin-panel",
3006
+ children: /* @__PURE__ */ o("div", {
3007
+ className: "admin-panel__unauthorized",
3008
+ children: [/* @__PURE__ */ a("h2", { children: "Sign in required" }), /* @__PURE__ */ a("p", { children: "You must be signed in to access the admin panel." })]
3009
+ })
3010
+ })
3011
+ });
3012
+ let A = m.permissions.includes(s.DELETE_ANY), j = m.permissions.includes(s.MANAGE_ROLES);
3013
+ return !A && !j ? /* @__PURE__ */ a("div", {
3014
+ className: k,
3015
+ children: /* @__PURE__ */ a("div", {
3016
+ className: "admin-panel",
3017
+ children: /* @__PURE__ */ o("div", {
3018
+ className: "admin-panel__unauthorized",
3019
+ children: [/* @__PURE__ */ a("h2", { children: "Access denied" }), /* @__PURE__ */ a("p", { children: "You do not have admin or moderation permissions for this space." })]
3020
+ })
3021
+ })
3022
+ }) : /* @__PURE__ */ a("div", {
3023
+ className: k,
3024
+ children: /* @__PURE__ */ o("div", {
3025
+ className: "admin-panel",
3026
+ children: [/* @__PURE__ */ o("div", {
3027
+ className: "admin-panel__header",
3028
+ children: [/* @__PURE__ */ o("h1", {
3029
+ className: "admin-panel__title",
3030
+ children: [
3031
+ h.split("-").map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(" "),
3032
+ " ",
3033
+ "— Admin"
3034
+ ]
3035
+ }), /* @__PURE__ */ a("p", {
3036
+ className: "admin-panel__subtitle",
3037
+ children: "Moderation and administration for this space."
3038
+ })]
3039
+ }), /* @__PURE__ */ o("div", {
3040
+ className: "admin-panel__sections",
3041
+ children: [
3042
+ j && /* @__PURE__ */ a("section", {
3043
+ className: "admin-panel__section",
3044
+ children: /* @__PURE__ */ o("div", {
3045
+ className: "admin-settings",
3046
+ children: [/* @__PURE__ */ o("button", {
3047
+ className: "admin-settings__toggle",
3048
+ onClick: () => v((e) => !e),
3049
+ children: [/* @__PURE__ */ a("span", { children: _ ? "▼" : "▶" }), /* @__PURE__ */ a("span", { children: "Sticker library" })]
3050
+ }), _ && /* @__PURE__ */ a("div", {
3051
+ className: "admin-settings__panel",
3052
+ children: /* @__PURE__ */ a(Y, {
3053
+ stationSlug: h,
3054
+ user: m,
3055
+ getToken: g
3056
+ })
3057
+ })]
3058
+ })
3059
+ }),
3060
+ j && /* @__PURE__ */ a("section", {
3061
+ className: "admin-panel__section",
3062
+ children: /* @__PURE__ */ o("div", {
3063
+ className: "admin-settings",
3064
+ children: [/* @__PURE__ */ o("button", {
3065
+ className: "admin-settings__toggle",
3066
+ onClick: () => b((e) => !e),
3067
+ children: [/* @__PURE__ */ a("span", { children: y ? "▼" : "▶" }), /* @__PURE__ */ a("span", { children: "Color theme" })]
3068
+ }), y && /* @__PURE__ */ a("div", {
3069
+ className: "admin-settings__panel",
3070
+ children: /* @__PURE__ */ a(ae, {
3071
+ stationSlug: h,
3072
+ getToken: g
3073
+ })
3074
+ })]
3075
+ })
3076
+ }),
3077
+ A && /* @__PURE__ */ a("section", {
3078
+ className: "admin-panel__section",
3079
+ children: /* @__PURE__ */ a(V, {
3080
+ stationSlug: h,
3081
+ user: m,
3082
+ getToken: g
3083
+ })
3084
+ }),
3085
+ A && /* @__PURE__ */ a("section", {
3086
+ className: "admin-panel__section",
3087
+ children: /* @__PURE__ */ a(W, {
3088
+ stationSlug: h,
3089
+ user: m,
3090
+ getToken: g
3091
+ })
3092
+ }),
3093
+ j && /* @__PURE__ */ a("section", {
3094
+ className: "admin-panel__section",
3095
+ children: /* @__PURE__ */ o("div", {
3096
+ className: "admin-settings",
3097
+ children: [/* @__PURE__ */ o("button", {
3098
+ className: "admin-settings__toggle",
3099
+ onClick: () => C((e) => !e),
3100
+ children: [/* @__PURE__ */ a("span", { children: S ? "▼" : "▶" }), /* @__PURE__ */ a("span", { children: "Moderator management" })]
3101
+ }), S && /* @__PURE__ */ a("div", {
3102
+ className: "admin-settings__panel",
3103
+ children: /* @__PURE__ */ a(ge, {
3104
+ stationSlug: h,
3105
+ user: m,
3106
+ getToken: g
3107
+ })
3108
+ })]
3109
+ })
3110
+ }),
3111
+ j && /* @__PURE__ */ a("section", {
3112
+ className: "admin-panel__section",
3113
+ children: /* @__PURE__ */ a(K, {
3114
+ stationSlug: h,
3115
+ user: m,
3116
+ getToken: g
3117
+ })
3118
+ }),
3119
+ j && /* @__PURE__ */ a("section", {
3120
+ className: "admin-panel__section",
3121
+ children: /* @__PURE__ */ o("div", {
3122
+ className: "admin-settings",
3123
+ children: [/* @__PURE__ */ o("button", {
3124
+ className: "admin-settings__toggle",
3125
+ onClick: () => O((e) => !e),
3126
+ children: [/* @__PURE__ */ a("span", { children: D ? "▼" : "▶" }), /* @__PURE__ */ a("span", { children: "Geo restrictions & IP bans" })]
3127
+ }), D && /* @__PURE__ */ a("div", {
3128
+ className: "admin-settings__panel",
3129
+ children: /* @__PURE__ */ a(pe, {
3130
+ stationSlug: h,
3131
+ getToken: g
3132
+ })
3133
+ })]
3134
+ })
3135
+ }),
3136
+ j && /* @__PURE__ */ a("section", {
3137
+ className: "admin-panel__section",
3138
+ children: /* @__PURE__ */ o("div", {
3139
+ className: "admin-settings",
3140
+ children: [/* @__PURE__ */ o("button", {
3141
+ className: "admin-settings__toggle",
3142
+ onClick: () => E((e) => !e),
3143
+ children: [/* @__PURE__ */ a("span", { children: T ? "▼" : "▶" }), /* @__PURE__ */ a("span", { children: "Chat history export" })]
3144
+ }), T && /* @__PURE__ */ a("div", {
3145
+ className: "admin-settings__panel",
3146
+ children: /* @__PURE__ */ a(ce, {
3147
+ stationSlug: h,
3148
+ getToken: g
3149
+ })
3150
+ })]
3151
+ })
3152
+ })
3153
+ ]
3154
+ })]
3155
+ })
3156
+ });
3157
+ }
3158
+ //#endregion
3159
+ export { s as S, u as _, W as a, l as b, F as c, O as d, D as f, x as g, T as h, K as i, P as l, w as m, ae as n, V as o, E as p, Y as r, R as s, _e as t, N as u, f as v, c as x, d as y };