import React from 'react' import { Check, X } from 'lucide-react' import { simplifyUrl } from '@/lib/url' /** * Format relay error messages to be more user-friendly */ function formatRelayError(error: string): string { const lowerError = error.toLowerCase() // Handle confusing relay error messages if (lowerError.includes('blocked') && lowerError.includes('event marked as protected')) { return 'Relay rejected this content (may be due to content policy)' } if (lowerError.includes('blocked')) { return 'Relay blocked this content' } if (lowerError.includes('rate limit') || lowerError.includes('rate-limit')) { return 'Rate limited - please wait before trying again' } if (lowerError.includes('auth') && lowerError.includes('required')) { return 'Authentication required' } if (lowerError.includes('writes disabled') || lowerError.includes('write disabled')) { return 'Relay has temporarily disabled writes' } if (lowerError.includes('invalid key')) { return 'Authentication failed - invalid key' } if (lowerError.includes('timeout')) { return 'Request timed out' } if (lowerError.includes('connection') && lowerError.includes('refused')) { return 'Connection refused by relay' } // Return original error if no specific formatting applies return error } /** * Render text with URLs as clickable hyperlinks */ function renderTextWithLinks(text: string): React.ReactNode { // URL regex pattern - matches http://, https://, ws://, wss:// URLs const urlRegex = /(https?:\/\/[^\s]+|wss?:\/\/[^\s]+)/gi const parts: React.ReactNode[] = [] let lastIndex = 0 let match: RegExpExecArray | null while ((match = urlRegex.exec(text)) !== null) { // Add text before the URL if (match.index > lastIndex) { parts.push(text.substring(lastIndex, match.index)) } // Add the URL as a link const url = match[0] parts.push( e.stopPropagation()} > {url} ) lastIndex = match.index + match[0].length } // Add remaining text if (lastIndex < text.length) { parts.push(text.substring(lastIndex)) } return parts.length > 0 ? <>{parts} : text } interface RelayStatus { url: string success: boolean error?: string message?: string authAttempted?: boolean } interface RelayStatusDisplayProps { relayStatuses: RelayStatus[] successCount: number totalCount: number className?: string /** * When `false`, hides the aggregate line. When a node, renders it instead of the default * “Published to …” copy (e.g. timeline REQ outcomes). */ aggregateSummary?: React.ReactNode | false } export default function RelayStatusDisplay({ relayStatuses, successCount, totalCount, className = '', aggregateSummary }: RelayStatusDisplayProps) { if (relayStatuses.length === 0) { return null } const defaultSummary = (
Published to {successCount} of {totalCount} relays
) return (
{aggregateSummary === false ? null : aggregateSummary !== undefined ? aggregateSummary : defaultSummary}
{relayStatuses.map((status, index) => (
{status.success ? ( ) : ( )}
{simplifyUrl(status.url)} {status.authAttempted && !status.success && ( (auth failed) )}
{!status.success && status.error && (
{renderTextWithLinks(formatRelayError(status.error))}
)} {status.success && status.message && (
{renderTextWithLinks(status.message)}
)}
))}
) }