Files
stack/apps/web/src/components/federation/ConnectionCard.tsx
Jason Woltje 5cf02e824b feat(#91): implement Connection Manager UI for federation
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>
2026-02-03 14:03:44 -06:00

153 lines
4.8 KiB
TypeScript

/**
* ConnectionCard Component
* Displays a single federation connection with PDA-friendly design
*/
import { FederationConnectionStatus, type ConnectionDetails } from "@/lib/api/federation";
interface ConnectionCardProps {
connection: ConnectionDetails;
onAccept?: (connectionId: string) => void;
onReject?: (connectionId: string) => void;
onDisconnect?: (connectionId: string) => void;
showDetails?: boolean;
compact?: boolean;
}
/**
* Get PDA-friendly status text and color
*/
function getStatusDisplay(status: FederationConnectionStatus): {
text: string;
colorClass: string;
icon: string;
} {
switch (status) {
case FederationConnectionStatus.ACTIVE:
return {
text: "Active",
colorClass: "text-green-600 bg-green-50",
icon: "🟢",
};
case FederationConnectionStatus.PENDING:
return {
text: "Pending",
colorClass: "text-blue-600 bg-blue-50",
icon: "🔵",
};
case FederationConnectionStatus.DISCONNECTED:
return {
text: "Disconnected",
colorClass: "text-yellow-600 bg-yellow-50",
icon: "⏸️",
};
case FederationConnectionStatus.REJECTED:
return {
text: "Rejected",
colorClass: "text-gray-600 bg-gray-50",
icon: "⚪",
};
}
}
export function ConnectionCard({
connection,
onAccept,
onReject,
onDisconnect,
showDetails = false,
compact = false,
}: ConnectionCardProps): React.JSX.Element {
const status = getStatusDisplay(connection.status);
const name =
typeof connection.metadata.name === "string" ? connection.metadata.name : "Remote Instance";
const description = connection.metadata.description as string | undefined;
const paddingClass = compact ? "p-3" : "p-4";
return (
<div
className={`border border-gray-200 rounded-lg ${paddingClass} hover:border-gray-300 transition-colors`}
>
{/* Header */}
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<h3 className="text-lg font-medium text-gray-900">{name}</h3>
<p className="text-sm text-gray-600 mt-1">{connection.remoteUrl}</p>
{description && <p className="text-sm text-gray-500 mt-1">{description}</p>}
</div>
{/* Status Badge */}
<div
className={`flex items-center gap-1 px-3 py-1 rounded-full text-sm font-medium ${status.colorClass}`}
>
<span>{status.icon}</span>
<span>{status.text}</span>
</div>
</div>
{/* Capabilities (when showDetails is true) */}
{showDetails && (
<div className="mt-3 pt-3 border-t border-gray-100">
<p className="text-xs font-medium text-gray-700 mb-2">Capabilities</p>
<div className="flex flex-wrap gap-2">
{connection.remoteCapabilities.supportsQuery && (
<span className="text-xs px-2 py-1 bg-gray-100 text-gray-700 rounded">Query</span>
)}
{connection.remoteCapabilities.supportsCommand && (
<span className="text-xs px-2 py-1 bg-gray-100 text-gray-700 rounded">Command</span>
)}
{connection.remoteCapabilities.supportsEvent && (
<span className="text-xs px-2 py-1 bg-gray-100 text-gray-700 rounded">Events</span>
)}
{connection.remoteCapabilities.supportsAgentSpawn && (
<span className="text-xs px-2 py-1 bg-gray-100 text-gray-700 rounded">
Agent Spawn
</span>
)}
</div>
</div>
)}
{/* Actions */}
{connection.status === FederationConnectionStatus.PENDING && (onAccept ?? onReject) && (
<div className="mt-4 flex gap-2">
{onAccept && (
<button
onClick={() => {
onAccept(connection.id);
}}
className="px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-md hover:bg-green-700 transition-colors"
>
Accept
</button>
)}
{onReject && (
<button
onClick={() => {
onReject(connection.id);
}}
className="px-4 py-2 bg-gray-200 text-gray-700 text-sm font-medium rounded-md hover:bg-gray-300 transition-colors"
>
Reject
</button>
)}
</div>
)}
{connection.status === FederationConnectionStatus.ACTIVE && onDisconnect && (
<div className="mt-4">
<button
onClick={() => {
onDisconnect(connection.id);
}}
className="px-4 py-2 bg-gray-200 text-gray-700 text-sm font-medium rounded-md hover:bg-gray-300 transition-colors"
>
Disconnect
</button>
</div>
)}
</div>
);
}