import React, { useState } from 'react'; import type { InstanceInfoResponse } from '@backspace/shared'; import { useInstanceStore, DifferentPasswordError } from '../../stores/instanceStore'; import { useAuthStore } from '../../stores/authStore'; // ─── Status indicator ──────────────────────────────────────────────────────── function StatusDot({ status }: { status: string }) { const colorClass = status === 'connected' ? 'bg-status-online' : status === 'connecting' ? 'bg-accent-amber' : 'bg-txt-tertiary'; return
; } // ─── Add Instance flow ─────────────────────────────────────────────────────── type AddStep = 'url' | 'auth' | 'done'; type AuthPhase = 'password' | 'fallback-login'; function AddInstanceFlow({ onDone }: { onDone: () => void }) { const user = useAuthStore((s) => s.user); const connectToRemote = useInstanceStore((s) => s.connectToRemote); const loginToRemote = useInstanceStore((s) => s.loginToRemote); const probeInstance = useInstanceStore((s) => s.probeInstance); const [step, setStep] = useState('url'); const [url, setUrl] = useState(''); const [probeResult, setProbeResult] = useState<(InstanceInfoResponse & { origin: string }) | null>(null); const [authPhase, setAuthPhase] = useState('password'); const [password, setPassword] = useState(''); const [fallbackUsername, setFallbackUsername] = useState(''); const [fallbackPassword, setFallbackPassword] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); const handleProbe = async () => { setError(''); setIsLoading(true); try { const result = await probeInstance(url); setProbeResult(result); setAuthPhase('password'); setStep('auth'); } catch (err) { setError((err as Error).message); } finally { setIsLoading(false); } }; const handleConnect = async () => { if (!probeResult) return; setError(''); setIsLoading(true); try { await connectToRemote( probeResult.origin, password, user?.displayName || undefined, ); setStep('done'); onDone(); } catch (err) { if (err instanceof DifferentPasswordError) { setAuthPhase('fallback-login'); setFallbackUsername(err.remoteUsername); setFallbackPassword(''); setError(''); } else { setError((err as Error).message); } } finally { setIsLoading(false); } }; const handleFallbackLogin = async () => { if (!probeResult) return; setError(''); setIsLoading(true); try { await loginToRemote(probeResult.origin, fallbackUsername, fallbackPassword); setStep('done'); onDone(); } catch (err) { setError((err as Error).message); } finally { setIsLoading(false); } }; if (step === 'done') return null; return (
{/* Step 1: Enter URL */} {step === 'url' && ( <>
Add Remote Instance
setUrl(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && !isLoading && url.trim() && handleProbe()} placeholder="https://instance.example.com" className="flex-1 px-3 py-2 bg-surface-input rounded text-txt-primary text-sm outline-none focus:ring-2 focus:ring-accent-primary" disabled={isLoading} />
)} {/* Step 2: Auth — single password */} {step === 'auth' && probeResult && authPhase === 'password' && ( <> {/* Instance info card */}
{probeResult.name}
{probeResult.origin}
setPassword(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && !isLoading && password && handleConnect()} placeholder="Your account password" className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary text-sm outline-none focus:ring-2 focus:ring-accent-primary" disabled={isLoading} autoFocus />
Your password is verified locally, then used to create or access your account on the remote instance.
)} {/* Step 2b: Fallback login — different password on remote */} {step === 'auth' && probeResult && authPhase === 'fallback-login' && ( <> {/* Instance info card */}
{probeResult.name}
{probeResult.origin}
An account already exists on this instance with a different password. Enter the credentials you used on that instance.
setFallbackUsername(e.target.value)} placeholder="Your username on this instance" className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary text-sm outline-none focus:ring-2 focus:ring-accent-primary" disabled={isLoading} />
setFallbackPassword(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && !isLoading && fallbackUsername && fallbackPassword && handleFallbackLogin()} placeholder="Password on the remote instance" className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary text-sm outline-none focus:ring-2 focus:ring-accent-primary" disabled={isLoading} autoFocus />
)} {/* Error display */} {error && (
{error}
)}
); } // ─── Main component ────────────────────────────────────────────────────────── export function ConnectedInstances() { const instances = useInstanceStore((s) => s.instances); const removeInstance = useInstanceStore((s) => s.removeInstance); const [showAddForm, setShowAddForm] = useState(false); return (

Connected Instances

{/* Home instance (always shown, non-removable) */}
Home Instance
{window.location.host}
Local
{/* Remote instances */} {instances.map((inst) => (
{inst.label}
{new URL(inst.origin).host} {inst.username && ( as {inst.username} )}
{inst.status === 'disconnected' && inst.error && (
{inst.error}
)}
))} {/* Add instance button / flow */} {showAddForm ? ( setShowAddForm(false)} /> ) : ( )}
); }