From c918199cee2bbb2000623a4570e61f50d31370ae Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Fri, 3 Apr 2026 02:48:35 +0200 Subject: [PATCH] feat: wire up DeleteIdentityDialog with S2S deletion modes and scope Update the dialog to support three deletion modes (leave/soft/full), scope selector with disabled "Select instances..." option, loading state during deletion, and per-instance error handling via toasts. --- .../components/modals/ConnectedInstances.tsx | 140 +++++++++++++----- 1 file changed, 107 insertions(+), 33 deletions(-) diff --git a/packages/web/src/components/modals/ConnectedInstances.tsx b/packages/web/src/components/modals/ConnectedInstances.tsx index ebfccc85..78770e66 100644 --- a/packages/web/src/components/modals/ConnectedInstances.tsx +++ b/packages/web/src/components/modals/ConnectedInstances.tsx @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom'; import type { InstanceInfoResponse, FederationRegistryEntry } from '@backspace/shared'; import { useInstanceStore, DifferentPasswordError } from '../../stores/instanceStore'; import { useAuthStore } from '../../stores/authStore'; +import { useUIStore } from '../../stores/uiStore'; import { isElectron } from '../../platform/platform'; import { ConfirmDialog } from '../ui/ConfirmDialog'; @@ -410,7 +411,7 @@ function RegistryFilterBar({ // ─── DeleteIdentityDialog ─────────────────────────────────────────────────── -type DeletionMode = 'leave' | 'nuke' | 'evaporate'; +type DeletionMode = 'leave' | 'soft' | 'full'; type DeletionScope = 'this' | 'select' | 'all'; function DeleteIdentityDialog({ @@ -423,19 +424,77 @@ function DeleteIdentityDialog({ onClose: () => void; }) { const deleteIdentity = useInstanceStore((s) => s.deleteIdentity); + const registry = useInstanceStore((s) => s.registry); const [mode, setMode] = useState('leave'); const [scope, setScope] = useState('this'); + const [loading, setLoading] = useState(false); - const handleConfirm = () => { - deleteIdentity([origin]); - onClose(); + const handleConfirm = async () => { + // Resolve target origins based on scope + let targetOrigins: string[]; + if (scope === 'all') { + targetOrigins = Array.from(registry.keys()); + } else { + targetOrigins = [origin]; + } + + if (targetOrigins.length === 0) { + onClose(); + return; + } + + if (mode !== 'leave') { + setLoading(true); + } + + const results = await deleteIdentity(targetOrigins, mode); + + // Check results + const failed = Object.entries(results).filter(([, r]) => !r.success); + if (failed.length === 0) { + useUIStore.getState().addToast( + mode === 'leave' + ? 'Disconnected successfully' + : targetOrigins.length === 1 + ? 'Identity deleted successfully' + : `Identity deleted on ${targetOrigins.length} instances`, + 'success', + 3000, + ); + onClose(); + } else { + for (const [failOrigin, result] of failed) { + let host: string; + try { host = new URL(failOrigin).hostname; } catch { host = failOrigin; } + if (result.error === 'owns_spaces') { + useUIStore.getState().addToast( + `${host}: Transfer space ownership first`, + 'warning', + 5000, + ); + } else { + useUIStore.getState().addToast( + `${host}: ${result.error || 'Failed'}`, + 'warning', + 5000, + ); + } + } + // Close if some succeeded, keep open if all failed + const succeeded = Object.values(results).filter(r => r.success).length; + if (succeeded > 0) { + onClose(); + } else { + setLoading(false); + } + } }; return ReactDOM.createPortal(

Delete Identity

@@ -449,45 +508,54 @@ function DeleteIdentityDialog({ - {/* Nuke everything */} + {/* Delete User (soft) */} + + {/* Nuke everything (full) */} + - - {/* Evaporate (coming soon) */} -
-
Evaporate
-
- Gradually fade your presence — coming soon. -
-
{/* Scope selector */} @@ -495,18 +563,22 @@ function DeleteIdentityDialog({
Scope
{([ - { key: 'this' as DeletionScope, label: 'This instance only' }, - { key: 'select' as DeletionScope, label: 'Select instances...' }, - { key: 'all' as DeletionScope, label: 'All remote instances' }, + { key: 'this' as DeletionScope, label: 'This instance only', disabled: false }, + { key: 'select' as DeletionScope, label: 'Select instances...', disabled: true }, + { key: 'all' as DeletionScope, label: 'All remote instances', disabled: false }, ]).map((opt) => (