From ca5cf61e53e96f87ec255c95505794d71ac143f9 Mon Sep 17 00:00:00 2001
From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com>
Date: Sun, 22 Mar 2026 04:48:32 +0100
Subject: [PATCH] fix: replace inline save banners with toast notifications
---
packages/web/src/components/modals/SpaceSettings.tsx | 10 ++--------
.../modals/instanceSettingsPanels/GeneralPanel.tsx | 12 ++++--------
.../modals/instanceSettingsPanels/StreamingPanel.tsx | 12 +++++-------
.../modals/spaceSettingsPanels/OverviewPanel.tsx | 8 +-------
.../modals/spaceSettingsPanels/RolesPanel.tsx | 10 +++-------
5 files changed, 15 insertions(+), 37 deletions(-)
diff --git a/packages/web/src/components/modals/SpaceSettings.tsx b/packages/web/src/components/modals/SpaceSettings.tsx
index 26e9864f..562ded73 100644
--- a/packages/web/src/components/modals/SpaceSettings.tsx
+++ b/packages/web/src/components/modals/SpaceSettings.tsx
@@ -23,9 +23,9 @@ function DiscoveryPanel({ spaceId }: { spaceId: string }) {
(space?.visibility as SpaceVisibility) ?? 'private'
);
const [description, setDescription] = useState(space?.description ?? '');
+ const addToast = useUIStore((s) => s.addToast);
const [saving, setSaving] = useState(false);
const [saveError, setSaveError] = useState('');
- const [saveSuccess, setSaveSuccess] = useState(false);
useEffect(() => {
if (space) {
@@ -43,11 +43,9 @@ function DiscoveryPanel({ spaceId }: { spaceId: string }) {
const handleSave = async () => {
setSaving(true);
setSaveError('');
- setSaveSuccess(false);
try {
await api.spaces.update(spaceId, { visibility, description: description.trim() });
- setSaveSuccess(true);
- setTimeout(() => setSaveSuccess(false), 2000);
+ addToast('Settings saved', 'success', 2000);
} catch (err) {
setSaveError(err instanceof Error ? err.message : 'Failed to save');
} finally {
@@ -126,10 +124,6 @@ function DiscoveryPanel({ spaceId }: { spaceId: string }) {
{saveError && (
{saveError}
)}
- {saveSuccess && (
- Settings saved
- )}
-
{/* Pending Join Requests — only shown when visibility is 'request' */}
{(visibility === 'request' || (space.visibility as SpaceVisibility) === 'request') && (
diff --git a/packages/web/src/components/modals/instanceSettingsPanels/GeneralPanel.tsx b/packages/web/src/components/modals/instanceSettingsPanels/GeneralPanel.tsx
index d8d56aa2..0c1d9b60 100644
--- a/packages/web/src/components/modals/instanceSettingsPanels/GeneralPanel.tsx
+++ b/packages/web/src/components/modals/instanceSettingsPanels/GeneralPanel.tsx
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react';
import { useSettingsStore } from '../../../stores/settingsStore';
+import { useUIStore } from '../../../stores/uiStore';
import { Toggle } from '../../ui/Toggle';
import type { InstanceAdminSettings } from '@backspace/shared';
@@ -7,10 +8,11 @@ export function GeneralPanel() {
const instanceSettings = useSettingsStore((s) => s.instanceSettings);
const updateInstanceSettings = useSettingsStore((s) => s.updateInstanceSettings);
+ const addToast = useUIStore((s) => s.addToast);
+
const [draft, setDraft] = useState(null);
const [saving, setSaving] = useState(false);
const [saveError, setSaveError] = useState('');
- const [saveSuccess, setSaveSuccess] = useState(false);
const [gifKeyDirty, setGifKeyDirty] = useState(false);
const [gifKeyDraft, setGifKeyDraft] = useState('');
@@ -34,7 +36,6 @@ export function GeneralPanel() {
const handleSave = async () => {
setSaving(true);
setSaveError('');
- setSaveSuccess(false);
try {
const payload: Partial = {
instanceName: draft!.instanceName,
@@ -47,8 +48,7 @@ export function GeneralPanel() {
await updateInstanceSettings(payload);
setGifKeyDirty(false);
setGifKeyDraft('');
- setSaveSuccess(true);
- setTimeout(() => setSaveSuccess(false), 2000);
+ addToast('Settings saved', 'success', 2000);
} catch (err) {
setSaveError(err instanceof Error ? err.message : 'Failed to save');
} finally {
@@ -151,10 +151,6 @@ export function GeneralPanel() {
{saveError && (
{saveError}
)}
- {saveSuccess && (
- Settings saved
- )}
-
{/* Save / Reset bar */}
{hasChanges && (
diff --git a/packages/web/src/components/modals/instanceSettingsPanels/StreamingPanel.tsx b/packages/web/src/components/modals/instanceSettingsPanels/StreamingPanel.tsx
index 3471336d..9674a3d2 100644
--- a/packages/web/src/components/modals/instanceSettingsPanels/StreamingPanel.tsx
+++ b/packages/web/src/components/modals/instanceSettingsPanels/StreamingPanel.tsx
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from 'react';
import { useSettingsStore } from '../../../stores/settingsStore';
+import { useUIStore } from '../../../stores/uiStore';
import type { InstanceStreamingLimits } from '@backspace/shared';
import {
STANDARD_RESOLUTIONS, STANDARD_FRAMERATES,
@@ -19,10 +20,12 @@ export function StreamingPanel() {
const limits = useSettingsStore((s) => s.streamingLimits);
const updateStreamingLimits = useSettingsStore((s) => s.updateStreamingLimits);
+ const addToast = useUIStore((s) => s.addToast);
+
const [draft, setDraft] = useState
(null);
const [saving, setSaving] = useState(false);
const [saveError, setSaveError] = useState('');
- const [saveSuccess, setSaveSuccess] = useState(false);
+
// Matrix editor state: full grid of kbps values (integers only)
const [matrixDraft, setMatrixDraft] = useState>({});
@@ -93,7 +96,6 @@ export function StreamingPanel() {
const handleSave = async () => {
setSaving(true);
setSaveError('');
- setSaveSuccess(false);
try {
// Compute sparse overrides: only cells that differ from defaults
const overrides: Record = {};
@@ -107,8 +109,7 @@ export function StreamingPanel() {
bitrateMatrixOverrides: Object.keys(overrides).length > 0 ? overrides : null,
};
await updateStreamingLimits(payload);
- setSaveSuccess(true);
- setTimeout(() => setSaveSuccess(false), 2000);
+ addToast('Settings saved', 'success', 2000);
} catch (err) {
setSaveError(err instanceof Error ? err.message : 'Failed to save');
} finally {
@@ -486,9 +487,6 @@ export function StreamingPanel() {
{saveError && (
{saveError}
)}
- {saveSuccess && (
- Settings saved
- )}
{hasChanges && (
diff --git a/packages/web/src/components/modals/spaceSettingsPanels/OverviewPanel.tsx b/packages/web/src/components/modals/spaceSettingsPanels/OverviewPanel.tsx
index 1088e9a3..3063e4ba 100644
--- a/packages/web/src/components/modals/spaceSettingsPanels/OverviewPanel.tsx
+++ b/packages/web/src/components/modals/spaceSettingsPanels/OverviewPanel.tsx
@@ -47,7 +47,6 @@ export function OverviewPanel({ spaceId }: OverviewPanelProps) {
const [saving, setSaving] = useState(false);
const [saveError, setSaveError] = useState('');
- const [saveSuccess, setSaveSuccess] = useState(false);
const [confirmDelete, setConfirmDelete] = useState(false);
const fileInputRef = useRef
(null);
@@ -174,7 +173,6 @@ export function OverviewPanel({ spaceId }: OverviewPanelProps) {
const handleSave = async () => {
setSaving(true);
setSaveError('');
- setSaveSuccess(false);
try {
const updates: { name?: string; icon?: string; banner?: string; avatarColor?: string } = {};
if (hasNameChange) updates.name = spaceName.trim();
@@ -198,8 +196,7 @@ export function OverviewPanel({ spaceId }: OverviewPanelProps) {
URL.revokeObjectURL(bannerPreview);
setBannerPreview(null);
}
- setSaveSuccess(true);
- setTimeout(() => setSaveSuccess(false), 2000);
+ addToast('Settings saved', 'success', 2000);
} catch (err) {
setSaveError(err instanceof Error ? err.message : 'Failed to save');
} finally {
@@ -449,9 +446,6 @@ export function OverviewPanel({ spaceId }: OverviewPanelProps) {
{saveError && (
{saveError}
)}
- {saveSuccess && (
- Settings saved
- )}
{/* Danger Zone */}
{isOwner && (
diff --git a/packages/web/src/components/modals/spaceSettingsPanels/RolesPanel.tsx b/packages/web/src/components/modals/spaceSettingsPanels/RolesPanel.tsx
index 055bef83..234f9542 100644
--- a/packages/web/src/components/modals/spaceSettingsPanels/RolesPanel.tsx
+++ b/packages/web/src/components/modals/spaceSettingsPanels/RolesPanel.tsx
@@ -1,5 +1,6 @@
import React, { useState, useRef, useEffect } from 'react';
import { useSpaceStore } from '../../../stores/spaceStore';
+import { useUIStore } from '../../../stores/uiStore';
import { api } from '../../../api/client';
import { PermissionBits, stringToPermissions, permissionsToString } from '../../../utils/permissions';
import type { Role } from '@backspace/shared';
@@ -227,10 +228,10 @@ function RoleEditView({ role, spaceId, isNew, onBack, onDeleted, onCopied }: Rol
const [draftPermissions, setDraftPermissions] = useState
(
stringToPermissions(role.permissions)
);
+ const addToast = useUIStore((s) => s.addToast);
const [saving, setSaving] = useState(false);
const [deleting, setDeleting] = useState(false);
const [saveError, setSaveError] = useState('');
- const [saveSuccess, setSaveSuccess] = useState(false);
const [confirmDelete, setConfirmDelete] = useState(false);
const hasNameChange = !isEveryone && draftName.trim() !== role.name;
@@ -246,7 +247,6 @@ function RoleEditView({ role, spaceId, isNew, onBack, onDeleted, onCopied }: Rol
setConfirmDelete(false);
setSaving(true);
setSaveError('');
- setSaveSuccess(false);
try {
const data: { name?: string; color?: string; permissions?: string } = {};
if (hasNameChange) data.name = draftName.trim();
@@ -254,8 +254,7 @@ function RoleEditView({ role, spaceId, isNew, onBack, onDeleted, onCopied }: Rol
if (hasPermChange) data.permissions = permissionsToString(draftPermissions);
await api.roles.update(spaceId, role.id, data);
await loadSpaceDetail(spaceId);
- setSaveSuccess(true);
- setTimeout(() => setSaveSuccess(false), 2000);
+ addToast('Role saved', 'success', 2000);
} catch (err) {
const msg = err instanceof Error ? err.message : 'Failed to save role';
if (msg.includes('already exists')) {
@@ -451,9 +450,6 @@ function RoleEditView({ role, spaceId, isNew, onBack, onDeleted, onCopied }: Rol
{saveError && (
{saveError}
)}
- {saveSuccess && (
- Role saved
- )}