diff --git a/packages/web/src/components/modals/ConnectedInstances.tsx b/packages/web/src/components/modals/ConnectedInstances.tsx index 85f68d7d..5cdc98ab 100644 --- a/packages/web/src/components/modals/ConnectedInstances.tsx +++ b/packages/web/src/components/modals/ConnectedInstances.tsx @@ -2,11 +2,16 @@ import React, { useState } from 'react'; import ReactDOM from 'react-dom'; import type { InstanceInfoResponse, FederationRegistryEntry } from '@backspace/shared'; import { useInstanceStore, DifferentPasswordError } from '../../stores/instanceStore'; -import type { ConnectedInstance } from '../../stores/instanceStore'; import { useAuthStore } from '../../stores/authStore'; import { isElectron } from '../../platform/platform'; import { ConfirmDialog } from '../ui/ConfirmDialog'; +// ─── URL helpers ───────────────────────────────────────────────────────────── + +function safeHost(origin: string): string { + try { return new URL(origin).host; } catch { return origin; } +} + // ─── Status indicator ──────────────────────────────────────────────────────── function StatusDot({ status }: { status: string }) { @@ -546,11 +551,16 @@ function RegistryRow({ const disconnectInstance = useInstanceStore((s) => s.disconnectInstance); const reconnectInstance = useInstanceStore((s) => s.reconnectInstance); const forceRemoveEntry = useInstanceStore((s) => s.forceRemoveEntry); + const reauthenticateInstance = useInstanceStore((s) => s.reauthenticateInstance); const [showForceRemoveConfirm, setShowForceRemoveConfirm] = useState(false); const [showDeleteIdentity, setShowDeleteIdentity] = useState(false); + const [showReauth, setShowReauth] = useState(false); + const [reauthPassword, setReauthPassword] = useState(''); + const [reauthLoading, setReauthLoading] = useState(false); + const [reauthError, setReauthError] = useState(''); - const name = entry.label || new URL(entry.origin).host; + const name = entry.label || safeHost(entry.origin); const isDisconnected = entry.status === 'disconnected'; const isConnected = entry.status === 'connected'; const hasIssue = entry.status === 'unreachable' || entry.status === 'auth_expired'; @@ -592,6 +602,21 @@ function RegistryRow({ setShowForceRemoveConfirm(false); }; + const handleReauth = async () => { + if (!reauthPassword) return; + setReauthError(''); + setReauthLoading(true); + try { + await reauthenticateInstance(entry.origin, reauthPassword); + setShowReauth(false); + setReauthPassword(''); + } catch (err) { + setReauthError((err as Error).message); + } finally { + setReauthLoading(false); + } + }; + return ( <>
@@ -610,7 +635,7 @@ function RegistryRow({
- {new URL(entry.origin).host} + {safeHost(entry.origin)} {entry.username && ( as {entry.username} )} @@ -661,6 +686,44 @@ function RegistryRow({
)} + {/* Re-auth inline form */} + {showReauth && ( +
{ e.preventDefault(); handleReauth(); }} className="mt-3 space-y-2"> + +
+ setReauthPassword(e.target.value)} + placeholder="Your account password" + className="input-standard flex-1 py-1.5" + disabled={reauthLoading} + autoFocus + autoComplete="current-password" + /> + + +
+ {reauthError && ( +
+ {reauthError} +
+ )} +
+ )} + {/* Actions */}
{isConnected && ( @@ -694,6 +757,13 @@ function RegistryRow({ Reconnect )} + )} + {entry.status === 'auth_expired' && ( + + )}