setGogDialogOpen(false)}>
diff --git a/web/src/plugins/game-library/game-library.css b/web/src/plugins/game-library/game-library.css
index 372c8f0..d4a9c7f 100644
--- a/web/src/plugins/game-library/game-library.css
+++ b/web/src/plugins/game-library/game-library.css
@@ -62,7 +62,7 @@
align-items: center;
gap: 8px;
padding: 6px 12px;
- border-radius: 20px;
+ border-radius: 4px;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
cursor: pointer;
@@ -234,7 +234,7 @@
gap: 6px;
background: var(--bg-tertiary);
padding: 6px 12px 6px 6px;
- border-radius: 20px;
+ border-radius: 4px;
cursor: pointer;
transition: all var(--transition);
}
@@ -698,7 +698,7 @@
}
.gl-sort-select option {
- background: #1a1a2e;
+ background: #1a1810;
color: #c7d5e0;
}
@@ -717,7 +717,7 @@
color: #8899a6;
border: 1px solid rgba(255, 255, 255, 0.08);
padding: 5px 12px;
- border-radius: 20px;
+ border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
@@ -776,8 +776,8 @@
}
.gl-dialog {
- background: #2a2a3e;
- border-radius: 12px;
+ background: #2a2620;
+ border-radius: 6px;
padding: 24px;
max-width: 500px;
width: 90%;
@@ -800,7 +800,7 @@
.gl-dialog-input {
width: 100%;
padding: 10px 12px;
- background: #1a1a2e;
+ background: #1a1810;
border: 1px solid #444;
border-radius: 8px;
color: #fff;
@@ -841,7 +841,7 @@
.gl-dialog-cancel {
padding: 8px 18px;
- background: #3a3a4e;
+ background: #322d26;
color: #ccc;
border: none;
border-radius: 8px;
@@ -850,7 +850,7 @@
}
.gl-dialog-cancel:hover {
- background: #4a4a5e;
+ background: #3a352d;
}
.gl-dialog-submit {
@@ -896,8 +896,8 @@
}
.gl-admin-panel {
- background: #2a2a3e;
- border-radius: 12px;
+ background: #2a2620;
+ border-radius: 6px;
padding: 0;
max-width: 600px;
width: 92%;
diff --git a/web/src/plugins/lolstats/lolstats.css b/web/src/plugins/lolstats/lolstats.css
index 59503c4..2bda0a2 100644
--- a/web/src/plugins/lolstats/lolstats.css
+++ b/web/src/plugins/lolstats/lolstats.css
@@ -70,7 +70,7 @@
gap: 6px;
padding: 4px 10px;
border: 1px solid var(--bg-tertiary);
- border-radius: 16px;
+ border-radius: 4px;
background: var(--bg-secondary);
color: var(--text-muted);
font-size: 12px;
@@ -94,13 +94,13 @@
align-items: center;
gap: 16px;
padding: 16px;
- border-radius: 12px;
+ border-radius: 6px;
background: var(--bg-secondary);
margin-bottom: 12px;
}
.lol-profile-icon {
width: 72px; height: 72px;
- border-radius: 12px;
+ border-radius: 6px;
border: 2px solid var(--bg-tertiary);
object-fit: cover;
}
@@ -170,7 +170,7 @@
.lol-ranked-card {
flex: 1;
padding: 12px 14px;
- border-radius: 10px;
+ border-radius: 6px;
background: var(--bg-secondary);
border-left: 4px solid var(--bg-tertiary);
}
@@ -517,7 +517,7 @@
.lol-tier-mode-btn {
padding: 6px 14px;
border: 1px solid var(--bg-tertiary);
- border-radius: 16px;
+ border-radius: 4px;
background: var(--bg-secondary);
color: var(--text-muted);
font-size: 12px;
diff --git a/web/src/plugins/soundboard/SoundboardTab.tsx b/web/src/plugins/soundboard/SoundboardTab.tsx
index 064951b..a58c165 100644
--- a/web/src/plugins/soundboard/SoundboardTab.tsx
+++ b/web/src/plugins/soundboard/SoundboardTab.tsx
@@ -186,25 +186,6 @@ async function apiGetVolume(guildId: string): Promise {
return typeof data?.volume === 'number' ? data.volume : 1;
}
-async function apiAdminStatus(): Promise {
- const res = await fetch(`${API_BASE}/admin/status`, { credentials: 'include' });
- if (!res.ok) return false;
- const data = await res.json();
- return !!data?.authenticated;
-}
-
-async function apiAdminLogin(password: string): Promise {
- const res = await fetch(`${API_BASE}/admin/login`, {
- method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include',
- body: JSON.stringify({ password })
- });
- return res.ok;
-}
-
-async function apiAdminLogout(): Promise {
- await fetch(`${API_BASE}/admin/logout`, { method: 'POST', credentials: 'include' });
-}
-
async function apiAdminDelete(paths: string[]): Promise {
const res = await fetch(`${API_BASE}/admin/sounds/delete`, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include',
@@ -324,13 +305,14 @@ interface VoiceStats {
interface SoundboardTabProps {
data: any;
+ isAdmin?: boolean;
}
/* ══════════════════════════════════════════════════════════════════
COMPONENT
══════════════════════════════════════════════════════════════════ */
-export default function SoundboardTab({ data }: SoundboardTabProps) {
+export default function SoundboardTab({ data, isAdmin: isAdminProp }: SoundboardTabProps) {
/* ── Data ── */
const [sounds, setSounds] = useState([]);
const [total, setTotal] = useState(0);
@@ -378,15 +360,7 @@ export default function SoundboardTab({ data }: SoundboardTabProps) {
const volDebounceRef = useRef>(undefined);
/* ── Admin ── */
- const [isAdmin, setIsAdmin] = useState(false);
- const [showAdmin, setShowAdmin] = useState(false);
- const [adminPwd, setAdminPwd] = useState('');
- const [adminSounds, setAdminSounds] = useState([]);
- const [adminLoading, setAdminLoading] = useState(false);
- const [adminQuery, setAdminQuery] = useState('');
- const [adminSelection, setAdminSelection] = useState>({});
- const [renameTarget, setRenameTarget] = useState('');
- const [renameValue, setRenameValue] = useState('');
+ const isAdmin = isAdminProp ?? false;
/* ── Drag & Drop Upload ── */
const [isDragging, setIsDragging] = useState(false);
@@ -521,7 +495,6 @@ export default function SoundboardTab({ data }: SoundboardTabProps) {
setSelected(match ? `${g}:${serverCid}` : `${ch[0].guildId}:${ch[0].channelId}`);
}
} catch (e: any) { notify(e?.message || 'Channel-Fehler', 'error'); }
- try { setIsAdmin(await apiAdminStatus()); } catch { }
try { const c = await fetchCategories(); setCategories(c.categories || []); } catch { }
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -656,13 +629,6 @@ export default function SoundboardTab({ data }: SoundboardTabProps) {
return () => document.removeEventListener('click', handler);
}, []);
- useEffect(() => {
- if (showAdmin && isAdmin) {
- void loadAdminSounds();
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [showAdmin, isAdmin]);
-
/* ── Actions ── */
async function loadAnalytics() {
try {
@@ -821,86 +787,6 @@ export default function SoundboardTab({ data }: SoundboardTabProps) {
setFavs(prev => ({ ...prev, [key]: !prev[key] }));
}
- async function loadAdminSounds() {
- setAdminLoading(true);
- try {
- const d = await fetchSounds('', '__all__', undefined, false);
- setAdminSounds(d.items || []);
- } catch (e: any) {
- notify(e?.message || 'Admin-Sounds konnten nicht geladen werden', 'error');
- } finally {
- setAdminLoading(false);
- }
- }
-
- function toggleAdminSelection(path: string) {
- setAdminSelection(prev => ({ ...prev, [path]: !prev[path] }));
- }
-
- function startRename(sound: Sound) {
- setRenameTarget(soundKey(sound));
- setRenameValue(sound.name);
- }
-
- function cancelRename() {
- setRenameTarget('');
- setRenameValue('');
- }
-
- async function submitRename() {
- if (!renameTarget) return;
- const baseName = renameValue.trim().replace(/\.(mp3|wav)$/i, '');
- if (!baseName) {
- notify('Bitte einen gueltigen Namen eingeben', 'error');
- return;
- }
- try {
- await apiAdminRename(renameTarget, baseName);
- notify('Sound umbenannt');
- cancelRename();
- setRefreshKey(k => k + 1);
- if (showAdmin) await loadAdminSounds();
- } catch (e: any) {
- notify(e?.message || 'Umbenennen fehlgeschlagen', 'error');
- }
- }
-
- async function deleteAdminPaths(paths: string[]) {
- if (paths.length === 0) return;
- try {
- await apiAdminDelete(paths);
- notify(paths.length === 1 ? 'Sound geloescht' : `${paths.length} Sounds geloescht`);
- setAdminSelection({});
- cancelRename();
- setRefreshKey(k => k + 1);
- if (showAdmin) await loadAdminSounds();
- } catch (e: any) {
- notify(e?.message || 'Loeschen fehlgeschlagen', 'error');
- }
- }
-
- async function handleAdminLogin() {
- try {
- const ok = await apiAdminLogin(adminPwd);
- if (ok) {
- setIsAdmin(true);
- setAdminPwd('');
- notify('Admin eingeloggt');
- }
- else notify('Falsches Passwort', 'error');
- } catch { notify('Login fehlgeschlagen', 'error'); }
- }
-
- async function handleAdminLogout() {
- try {
- await apiAdminLogout();
- setIsAdmin(false);
- setAdminSelection({});
- cancelRename();
- notify('Ausgeloggt');
- } catch { }
- }
-
/* ── Computed ── */
const displaySounds = useMemo(() => {
if (activeTab === 'favorites') return sounds.filter(s => favs[s.relativePath ?? s.fileName]);
@@ -938,26 +824,6 @@ export default function SoundboardTab({ data }: SoundboardTabProps) {
return groups;
}, [channels]);
- const adminFilteredSounds = useMemo(() => {
- const q = adminQuery.trim().toLowerCase();
- if (!q) return adminSounds;
- return adminSounds.filter(s => {
- const key = soundKey(s).toLowerCase();
- return s.name.toLowerCase().includes(q)
- || (s.folder || '').toLowerCase().includes(q)
- || key.includes(q);
- });
- }, [adminQuery, adminSounds, soundKey]);
-
- const selectedAdminPaths = useMemo(() =>
- Object.keys(adminSelection).filter(k => adminSelection[k]),
- [adminSelection]);
-
- const selectedVisibleCount = useMemo(() =>
- adminFilteredSounds.filter(s => !!adminSelection[soundKey(s)]).length,
- [adminFilteredSounds, adminSelection, soundKey]);
-
- const allVisibleSelected = adminFilteredSounds.length > 0 && selectedVisibleCount === adminFilteredSounds.length;
const analyticsTop = analytics.mostPlayed.slice(0, 10);
const totalSoundsDisplay = analytics.totalSounds || total;
@@ -1040,13 +906,6 @@ export default function SoundboardTab({ data }: SoundboardTabProps) {
)}
)}
- {
const path = ctxMenu.sound.relativePath ?? ctxMenu.sound.fileName;
- await deleteAdminPaths([path]);
+ if (!window.confirm(`Sound "${ctxMenu.sound.name}" loeschen?`)) { setCtxMenu(null); return; }
+ try {
+ await apiAdminDelete([path]);
+ notify('Sound geloescht');
+ setRefreshKey(k => k + 1);
+ } catch (e: any) {
+ notify(e?.message || 'Loeschen fehlgeschlagen', 'error');
+ }
setCtxMenu(null);
}}>
delete
@@ -1437,159 +1303,6 @@ export default function SoundboardTab({ data }: SoundboardTabProps) {
)}
- {/* ═══ ADMIN PANEL ═══ */}
- {showAdmin && (
-
diff --git a/web/src/plugins/soundboard/soundboard.css b/web/src/plugins/soundboard/soundboard.css
index be47e1a..4abee5f 100644
--- a/web/src/plugins/soundboard/soundboard.css
+++ b/web/src/plugins/soundboard/soundboard.css
@@ -6,10 +6,10 @@
Theme Variables — Default (Discord Blurple)
──────────────────────────────────────────── */
.sb-app {
- --bg-deep: #1a1b1e;
- --bg-primary: #1e1f22;
- --bg-secondary: #2b2d31;
- --bg-tertiary: #313338;
+ --bg-deep: #1a1810;
+ --bg-primary: #211e17;
+ --bg-secondary: #2a2620;
+ --bg-tertiary: #322d26;
--bg-modifier-hover: rgba(79, 84, 92, .16);
--bg-modifier-active: rgba(79, 84, 92, .24);
--bg-modifier-selected: rgba(79, 84, 92, .32);
@@ -183,7 +183,7 @@
gap: 8px;
padding: 5px 12px 5px 10px;
border: 1px solid rgba(255, 255, 255, .08);
- border-radius: 20px;
+ border-radius: 4px;
background: var(--bg-tertiary);
color: var(--text-normal);
font-family: var(--font);
@@ -283,7 +283,7 @@
align-items: center;
gap: 6px;
padding: 4px 10px;
- border-radius: 20px;
+ border-radius: 4px;
background: rgba(35, 165, 90, .12);
font-size: 12px;
color: var(--green);
@@ -295,13 +295,12 @@
height: 7px;
border-radius: 50%;
background: var(--green);
- box-shadow: 0 0 6px rgba(35, 165, 90, .6);
animation: pulse-dot 2s ease-in-out infinite;
}
@keyframes pulse-dot {
- 0%, 100% { box-shadow: 0 0 6px rgba(35, 165, 90, .5); }
- 50% { box-shadow: 0 0 12px rgba(35, 165, 90, .8); }
+ 0%, 100% { opacity: .7; }
+ 50% { opacity: 1; }
}
.conn-ping {
@@ -325,7 +324,7 @@
.conn-modal {
background: var(--bg-primary);
border: 1px solid var(--border);
- border-radius: 16px;
+ border-radius: 4px;
width: 340px;
box-shadow: 0 20px 60px rgba(0,0,0,.4);
overflow: hidden;
@@ -437,7 +436,7 @@
align-items: center;
gap: 6px;
padding: 6px 14px;
- border-radius: 20px;
+ border-radius: 4px;
background: var(--bg-tertiary);
color: var(--text-muted);
font-family: var(--font);
@@ -490,7 +489,7 @@
height: 32px;
padding: 0 28px 0 32px;
border: 1px solid rgba(255, 255, 255, .06);
- border-radius: 20px;
+ border-radius: 4px;
background: var(--bg-secondary);
color: var(--text-normal);
font-family: var(--font);
@@ -541,7 +540,7 @@
max-width: 460px;
flex: 1;
padding: 4px 6px 4px 8px;
- border-radius: 20px;
+ border-radius: 4px;
background: var(--bg-secondary);
border: 1px solid rgba(255, 255, 255, .08);
}
@@ -571,7 +570,7 @@
.url-import-btn {
height: 24px;
padding: 0 10px;
- border-radius: 14px;
+ border-radius: 6px;
border: 1px solid rgba(var(--accent-rgb, 88, 101, 242), .45);
background: rgba(var(--accent-rgb, 88, 101, 242), .12);
color: var(--accent);
@@ -617,7 +616,7 @@
gap: 6px;
padding: 6px 12px;
border: 1px solid rgba(255, 255, 255, .08);
- border-radius: 20px;
+ border-radius: 4px;
background: var(--bg-tertiary);
color: var(--text-muted);
font-family: var(--font);
@@ -656,20 +655,20 @@
.tb-btn.party:hover {
background: var(--yellow);
- color: #1a1b1e;
+ color: #1a1810;
border-color: var(--yellow);
}
.tb-btn.party.active {
background: var(--yellow);
- color: #1a1b1e;
+ color: #1a1810;
border-color: var(--yellow);
animation: party-btn 600ms ease-in-out infinite alternate;
}
@keyframes party-btn {
- from { box-shadow: 0 0 8px rgba(240, 178, 50, .4); }
- to { box-shadow: 0 0 20px rgba(240, 178, 50, .7); }
+ from { opacity: .85; }
+ to { opacity: 1; }
}
.tb-btn.stop {
@@ -689,7 +688,7 @@
align-items: center;
gap: 6px;
padding: 4px 10px;
- border-radius: 20px;
+ border-radius: 4px;
background: var(--bg-tertiary);
border: 1px solid rgba(255, 255, 255, .06);
}
@@ -739,7 +738,7 @@
align-items: center;
gap: 4px;
padding: 4px 8px;
- border-radius: 20px;
+ border-radius: 4px;
background: var(--bg-tertiary);
border: 1px solid rgba(255, 255, 255, .06);
}
@@ -759,7 +758,6 @@
.theme-dot.active {
border-color: var(--white);
- box-shadow: 0 0 6px rgba(255, 255, 255, .3);
}
/* ── Analytics Strip ── */
@@ -778,7 +776,7 @@
align-items: center;
gap: 8px;
padding: 8px 12px;
- border-radius: 12px;
+ border-radius: 6px;
background: var(--bg-secondary);
border: 1px solid rgba(255, 255, 255, .08);
}
@@ -871,7 +869,7 @@
align-items: center;
gap: 6px;
padding: 4px 12px;
- border-radius: 20px;
+ border-radius: 4px;
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
@@ -946,28 +944,13 @@
animation: card-enter 350ms ease-out forwards;
}
-.sound-card::before {
- content: '';
- position: absolute;
- inset: 0;
- border-radius: inherit;
- opacity: 0;
- transition: opacity var(--transition);
- background: radial-gradient(ellipse at center, var(--accent-glow) 0%, transparent 70%);
- pointer-events: none;
-}
-
.sound-card:hover {
background: var(--bg-tertiary);
transform: translateY(-3px);
- box-shadow: var(--shadow-med), 0 0 20px var(--accent-glow);
+ box-shadow: var(--shadow-med);
border-color: rgba(88, 101, 242, .2);
}
-.sound-card:hover::before {
- opacity: 1;
-}
-
.sound-card:active {
transform: translateY(0);
transition-duration: 50ms;
@@ -975,12 +958,7 @@
.sound-card.playing {
border-color: var(--accent);
- animation: card-enter 350ms ease-out forwards, playing-glow 1.2s ease-in-out infinite alternate;
-}
-
-@keyframes playing-glow {
- from { box-shadow: 0 0 4px var(--accent-glow); }
- to { box-shadow: 0 0 16px var(--accent-glow); }
+ animation: card-enter 350ms ease-out forwards;
}
@keyframes card-enter {
@@ -1170,7 +1148,7 @@
align-items: center;
gap: 6px;
padding: 4px 12px;
- border-radius: 20px;
+ border-radius: 4px;
background: rgba(var(--accent-rgb, 88, 101, 242), .12);
border: 1px solid rgba(var(--accent-rgb, 88, 101, 242), .2);
font-size: 12px;
@@ -1221,7 +1199,7 @@
align-items: center;
gap: 6px;
padding: 4px 10px;
- border-radius: 20px;
+ border-radius: 4px;
background: var(--bg-tertiary);
border: 1px solid rgba(255, 255, 255, .06);
}
@@ -1301,20 +1279,7 @@
content: '';
position: absolute;
inset: 0;
- background: linear-gradient(45deg,
- rgba(255, 0, 0, .04),
- rgba(0, 255, 0, .04),
- rgba(0, 0, 255, .04),
- rgba(255, 255, 0, .04)
- );
- background-size: 400% 400%;
- animation: party-grad 3s ease infinite;
-}
-
-@keyframes party-grad {
- 0% { background-position: 0% 50%; }
- 50% { background-position: 100% 50%; }
- 100% { background-position: 0% 50%; }
+ background: rgba(255, 255, 255, .03);
}
@keyframes party-hue {
@@ -1386,7 +1351,7 @@
left: 50%;
transform: translateX(-50%);
padding: 10px 20px;
- border-radius: 20px;
+ border-radius: 4px;
font-size: 13px;
font-weight: 600;
z-index: 100;
@@ -1602,7 +1567,7 @@
justify-content: space-between;
gap: 10px;
padding: 8px 10px;
- border-radius: 10px;
+ border-radius: 6px;
background: var(--bg-tertiary);
border: 1px solid rgba(255, 255, 255, .08);
flex-wrap: wrap;
@@ -1626,7 +1591,7 @@
max-height: 52vh;
overflow-y: auto;
border: 1px solid rgba(255, 255, 255, .08);
- border-radius: 10px;
+ border-radius: 6px;
background: var(--bg-primary);
}
@@ -1852,7 +1817,7 @@
align-items: center;
gap: 14px;
padding: 64px 72px;
- border-radius: 24px;
+ border-radius: 6px;
border: 2.5px dashed rgba(var(--accent-rgb), .55);
background: rgba(var(--accent-rgb), .07);
animation: drop-pulse 2.2s ease-in-out infinite;
@@ -1861,11 +1826,9 @@
@keyframes drop-pulse {
0%, 100% {
border-color: rgba(var(--accent-rgb), .45);
- box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0);
}
50% {
border-color: rgba(var(--accent-rgb), .9);
- box-shadow: 0 0 60px 12px rgba(var(--accent-rgb), .12);
}
}
@@ -1902,7 +1865,7 @@
width: 340px;
background: var(--bg-secondary);
border: 1px solid rgba(255, 255, 255, .09);
- border-radius: 14px;
+ border-radius: 6px;
box-shadow: 0 8px 40px rgba(0, 0, 0, .45);
z-index: 200;
animation: slide-up 200ms cubic-bezier(.16,1,.3,1);
@@ -2048,7 +2011,7 @@
width: 420px; max-width: 92vw;
background: var(--bg-secondary);
border: 1px solid rgba(255, 255, 255, .1);
- border-radius: 16px;
+ border-radius: 4px;
box-shadow: 0 12px 60px rgba(0, 0, 0, .5);
animation: scale-in 200ms cubic-bezier(.16, 1, .3, 1);
}
diff --git a/web/src/plugins/streaming/StreamingTab.tsx b/web/src/plugins/streaming/StreamingTab.tsx
index cd85e68..a511798 100644
--- a/web/src/plugins/streaming/StreamingTab.tsx
+++ b/web/src/plugins/streaming/StreamingTab.tsx
@@ -46,23 +46,22 @@ function formatElapsed(startedAt: string): string {
// ── Quality Presets ──
const QUALITY_PRESETS = [
- { label: '720p30', width: 1280, height: 720, fps: 30, bitrate: 2_500_000 },
- { label: '1080p30', width: 1920, height: 1080, fps: 30, bitrate: 5_000_000 },
- { label: '1080p60', width: 1920, height: 1080, fps: 60, bitrate: 8_000_000 },
- { label: '1440p60', width: 2560, height: 1440, fps: 60, bitrate: 14_000_000 },
- { label: '4K60', width: 3840, height: 2160, fps: 60, bitrate: 25_000_000 },
- { label: '4K165 Ultra', width: 3840, height: 2160, fps: 165, bitrate: 50_000_000 },
+ { label: 'Niedrig \u00B7 4 Mbit \u00B7 60fps', fps: 60, bitrate: 4_000_000 },
+ { label: 'Mittel \u00B7 8 Mbit \u00B7 60fps', fps: 60, bitrate: 8_000_000 },
+ { label: 'Hoch \u00B7 14 Mbit \u00B7 60fps', fps: 60, bitrate: 14_000_000 },
+ { label: 'Ultra \u00B7 25 Mbit \u00B7 60fps', fps: 60, bitrate: 25_000_000 },
+ { label: 'Max \u00B7 50 Mbit \u00B7 165fps', fps: 165, bitrate: 50_000_000 },
] as const;
// ── Component ──
-export default function StreamingTab({ data }: { data: any }) {
+export default function StreamingTab({ data, isAdmin: isAdminProp }: { data: any; isAdmin?: boolean }) {
// ── State ──
const [streams, setStreams] = useState
([]);
const [userName, setUserName] = useState(() => localStorage.getItem('streaming_name') || '');
const [streamTitle, setStreamTitle] = useState('Screen Share');
const [streamPassword, setStreamPassword] = useState('');
- const [qualityIdx, setQualityIdx] = useState(2); // Default: 1080p60
+ const [qualityIdx, setQualityIdx] = useState(1); // Default: 1080p60
const [error, setError] = useState(null);
const [joinModal, setJoinModal] = useState(null);
const [myStreamId, setMyStreamId] = useState(null);
@@ -73,16 +72,9 @@ export default function StreamingTab({ data }: { data: any }) {
const [openMenu, setOpenMenu] = useState(null);
const [copiedId, setCopiedId] = useState(null);
- // ── Admin / Notification Config ──
- const [showAdmin, setShowAdmin] = useState(false);
- const [isAdmin, setIsAdmin] = useState(false);
- const [adminPwd, setAdminPwd] = useState('');
- const [adminError, setAdminError] = useState('');
- const [availableChannels, setAvailableChannels] = useState>([]);
- const [notifyConfig, setNotifyConfig] = useState>([]);
- const [configLoading, setConfigLoading] = useState(false);
- const [configSaving, setConfigSaving] = useState(false);
- const [notifyStatus, setNotifyStatus] = useState<{ online: boolean; botTag: string | null }>({ online: false, botTag: null });
+ // ── Admin ──
+ const _isAdmin = isAdminProp ?? false;
+ void _isAdmin; // kept for potential future use
// ── Refs ──
const wsRef = useRef(null);
@@ -100,7 +92,7 @@ export default function StreamingTab({ data }: { data: any }) {
// Refs that mirror state (avoid stale closures in WS handler)
const isBroadcastingRef = useRef(false);
const viewingRef = useRef(null);
- const qualityRef = useRef(QUALITY_PRESETS[2]);
+ const qualityRef = useRef(QUALITY_PRESETS[1]);
useEffect(() => { isBroadcastingRef.current = isBroadcasting; }, [isBroadcasting]);
useEffect(() => { viewingRef.current = viewing; }, [viewing]);
useEffect(() => { qualityRef.current = QUALITY_PRESETS[qualityIdx]; }, [qualityIdx]);
@@ -138,17 +130,6 @@ export default function StreamingTab({ data }: { data: any }) {
return () => document.removeEventListener('click', handler);
}, [openMenu]);
- // Check admin status on mount
- useEffect(() => {
- fetch('/api/notifications/admin/status', { credentials: 'include' })
- .then(r => r.json())
- .then(d => setIsAdmin(d.admin === true))
- .catch(() => {});
- fetch('/api/notifications/status')
- .then(r => r.json())
- .then(d => setNotifyStatus(d))
- .catch(() => {});
- }, []);
// ── Send via WS ──
const wsSend = useCallback((d: Record) => {
@@ -422,7 +403,7 @@ export default function StreamingTab({ data }: { data: any }) {
try {
const q = qualityRef.current;
const stream = await navigator.mediaDevices.getDisplayMedia({
- video: { frameRate: { ideal: q.fps }, width: { ideal: q.width }, height: { ideal: q.height } },
+ video: { frameRate: { ideal: q.fps } },
audio: true,
});
localStreamRef.current = stream;
@@ -610,97 +591,6 @@ export default function StreamingTab({ data }: { data: any }) {
setOpenMenu(null);
}, [buildStreamLink]);
- // ── Admin functions ──
- const adminLogin = useCallback(async () => {
- setAdminError('');
- try {
- const resp = await fetch('/api/notifications/admin/login', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ password: adminPwd }),
- credentials: 'include',
- });
- if (resp.ok) {
- setIsAdmin(true);
- setAdminPwd('');
- loadNotifyConfig();
- } else {
- const d = await resp.json();
- setAdminError(d.error || 'Fehler');
- }
- } catch {
- setAdminError('Verbindung fehlgeschlagen');
- }
- }, [adminPwd]);
-
- const adminLogout = useCallback(async () => {
- await fetch('/api/notifications/admin/logout', { method: 'POST', credentials: 'include' });
- setIsAdmin(false);
- setShowAdmin(false);
- }, []);
-
- const loadNotifyConfig = useCallback(async () => {
- setConfigLoading(true);
- try {
- const [chResp, cfgResp] = await Promise.all([
- fetch('/api/notifications/channels', { credentials: 'include' }),
- fetch('/api/notifications/config', { credentials: 'include' }),
- ]);
- if (chResp.ok) {
- const chData = await chResp.json();
- setAvailableChannels(chData.channels || []);
- }
- if (cfgResp.ok) {
- const cfgData = await cfgResp.json();
- setNotifyConfig(cfgData.channels || []);
- }
- } catch { /* silent */ }
- finally { setConfigLoading(false); }
- }, []);
-
- const openAdmin = useCallback(() => {
- setShowAdmin(true);
- if (isAdmin) loadNotifyConfig();
- }, [isAdmin, loadNotifyConfig]);
-
- const toggleChannelEvent = useCallback((channelId: string, channelName: string, guildId: string, guildName: string, event: string) => {
- setNotifyConfig(prev => {
- const existing = prev.find(c => c.channelId === channelId);
- if (existing) {
- const hasEvent = existing.events.includes(event);
- const newEvents = hasEvent
- ? existing.events.filter(e => e !== event)
- : [...existing.events, event];
- if (newEvents.length === 0) {
- return prev.filter(c => c.channelId !== channelId);
- }
- return prev.map(c => c.channelId === channelId ? { ...c, events: newEvents } : c);
- } else {
- return [...prev, { channelId, channelName, guildId, guildName, events: [event] }];
- }
- });
- }, []);
-
- const saveNotifyConfig = useCallback(async () => {
- setConfigSaving(true);
- try {
- const resp = await fetch('/api/notifications/config', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ channels: notifyConfig }),
- credentials: 'include',
- });
- if (resp.ok) {
- // brief visual feedback handled by configSaving state
- }
- } catch { /* silent */ }
- finally { setConfigSaving(false); }
- }, [notifyConfig]);
-
- const isChannelEventEnabled = useCallback((channelId: string, event: string): boolean => {
- const ch = notifyConfig.find(c => c.channelId === channelId);
- return ch?.events.includes(event) ?? false;
- }, [notifyConfig]);
// ── Render ──
@@ -754,39 +644,50 @@ export default function StreamingTab({ data }: { data: any }) {
)}
- setUserName(e.target.value)}
- disabled={isBroadcasting}
- />
- setStreamTitle(e.target.value)}
- disabled={isBroadcasting}
- />
- setStreamPassword(e.target.value)}
- disabled={isBroadcasting}
- />
-
+
+
+
+
{isBroadcasting ? (
)}
-
{streams.length === 0 && !isBroadcasting ? (
@@ -903,100 +801,6 @@ export default function StreamingTab({ data }: { data: any }) {
)}
- {/* ── Notification Admin Modal ── */}
- {showAdmin && (
-