import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; import { useNavigate } from 'react-router-dom'; import type { InstanceInfoResponse, FederationRegistryEntry, PeeringSubscription, PeeringNotification, PeeringTriggerReason, } from '@backspace/shared'; import { useInstanceStore, DifferentPasswordError, isSelfOrigin } from '../../stores/instanceStore'; import { useAuthStore } from '../../stores/authStore'; import { useUIStore } from '../../stores/uiStore'; import { useFederationStore } from '../../stores/federationStore'; 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 }) { const colorClass = status === 'connected' ? 'bg-status-online' : status === 'connecting' ? 'bg-accent-amber' : 'bg-txt-tertiary'; return
; } // ─── Registry status helpers ──────────────────────────────────────────────── function registryStatusColor(status: string): string { switch (status) { case 'connected': return 'bg-status-online/15 text-status-online'; case 'disconnected': return 'bg-white/5 text-txt-tertiary'; case 'unreachable': return 'bg-accent-amber/15 text-accent-amber'; case 'auth_expired': return 'bg-accent-rose/15 text-accent-rose'; default: return 'bg-white/5 text-txt-tertiary'; } } function registryStatusDotColor(status: string): string { switch (status) { case 'connected': return 'bg-status-online'; case 'unreachable': return 'bg-accent-amber'; case 'auth_expired': return 'bg-accent-rose'; default: return 'bg-txt-tertiary'; } } function registryStatusLabel(status: string): string { switch (status) { case 'connected': return 'Connected'; case 'disconnected': return 'Disconnected'; case 'unreachable': return 'Unreachable'; case 'auth_expired': return 'Auth Expired'; default: return status; } } function formatRelativeTime(timestamp: number | null): string { if (!timestamp) return 'Never'; const seconds = Math.floor((Date.now() - timestamp) / 1000); if (seconds < 60) return 'Just now'; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); return `${days}d ago`; } function formatAbsoluteDate(timestamp: number): string { return new Date(timestamp).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); } // ─── 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] = useStateRemove your federated identity on {label}. Choose how your data should be handled.
{/* Deletion mode selection */}Your admin must approve before these requests can proceed.
Link accounts across federated Backspace instances.