Implemented comprehensive UI for managing federation connections: Features: - View existing federation connections grouped by status - Initiate new connections to remote instances - Accept/reject pending connection requests - Disconnect active connections - Display connection status, metadata, and capabilities - PDA-friendly design throughout (no demanding language) Components: - ConnectionCard: Display individual connections with actions - ConnectionList: Grouped list view with status sections - InitiateConnectionDialog: Modal for connecting to new instances - Connections page: Main management interface Implementation: - Full test coverage (42 tests, 100% passing) - TypeScript strict mode compliance - ESLint passing with no warnings - Mock data for development (ready for backend integration) - Proper error handling and loading states - PDA-friendly language (calm, supportive, stress-free) Status indicators: - 🟢 Active (soft green) - 🔵 Pending (soft blue) - ⏸️ Disconnected (soft yellow) - ⚪ Rejected (light gray) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
130 lines
3.7 KiB
TypeScript
130 lines
3.7 KiB
TypeScript
/**
|
|
* InitiateConnectionDialog Component
|
|
* Dialog for initiating a new federation connection
|
|
*/
|
|
|
|
import { useState, useEffect } from "react";
|
|
|
|
interface InitiateConnectionDialogProps {
|
|
open: boolean;
|
|
onInitiate: (url: string) => void;
|
|
onCancel: () => void;
|
|
isLoading?: boolean;
|
|
error?: string;
|
|
}
|
|
|
|
/**
|
|
* Validate if a string is a valid URL
|
|
*/
|
|
function isValidUrl(url: string): boolean {
|
|
try {
|
|
const parsedUrl = new URL(url);
|
|
return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:";
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function InitiateConnectionDialog({
|
|
open,
|
|
onInitiate,
|
|
onCancel,
|
|
isLoading = false,
|
|
error,
|
|
}: InitiateConnectionDialogProps): React.JSX.Element | null {
|
|
const [url, setUrl] = useState("");
|
|
const [validationError, setValidationError] = useState("");
|
|
|
|
// Clear input when dialog closes
|
|
useEffect(() => {
|
|
if (!open) {
|
|
setUrl("");
|
|
setValidationError("");
|
|
}
|
|
}, [open]);
|
|
|
|
if (!open) {
|
|
return null;
|
|
}
|
|
|
|
const handleConnect = (): void => {
|
|
// Validate URL
|
|
if (!url.trim()) {
|
|
setValidationError("Please enter a URL");
|
|
return;
|
|
}
|
|
|
|
if (!isValidUrl(url)) {
|
|
setValidationError("Please enter a valid URL (must start with http:// or https://)");
|
|
return;
|
|
}
|
|
|
|
setValidationError("");
|
|
onInitiate(url);
|
|
};
|
|
|
|
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>): void => {
|
|
if (e.key === "Enter" && url.trim() && !isLoading) {
|
|
handleConnect();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
|
<div className="bg-white rounded-lg max-w-md w-full p-6">
|
|
{/* Header */}
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-2">Connect to Remote Instance</h2>
|
|
<p className="text-sm text-gray-600 mb-6">
|
|
Enter the URL of the Mosaic Stack instance you'd like to connect to
|
|
</p>
|
|
|
|
{/* URL Input */}
|
|
<div className="mb-4">
|
|
<label htmlFor="instance-url" className="block text-sm font-medium text-gray-700 mb-2">
|
|
Instance URL
|
|
</label>
|
|
<input
|
|
id="instance-url"
|
|
type="url"
|
|
value={url}
|
|
onChange={(e) => {
|
|
setUrl(e.target.value);
|
|
setValidationError("");
|
|
}}
|
|
onKeyDown={handleKeyPress}
|
|
disabled={isLoading}
|
|
placeholder="https://mosaic.example.com"
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
/>
|
|
{validationError && <p className="text-sm text-red-600 mt-1">{validationError}</p>}
|
|
</div>
|
|
|
|
{/* Error Message */}
|
|
{error && (
|
|
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-md">
|
|
<p className="text-sm text-red-600">{error}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Actions */}
|
|
<div className="flex gap-3 justify-end">
|
|
<button
|
|
onClick={onCancel}
|
|
disabled={isLoading}
|
|
className="px-4 py-2 text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={handleConnect}
|
|
disabled={!url.trim() || isLoading}
|
|
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{isLoading ? "Connecting..." : "Connect"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|