'use client' import { useState, useEffect } from 'react' import { motion } from 'framer-motion' import { ExternalLink, Zap, CheckCircle, AlertCircle, Brain, Code, BookOpen, Cloud, Layers, Loader2, WifiOff, Clock, FileText, RefreshCw, } from 'lucide-react' import Link from 'next/link' import { SiteEntry, VerificationResult } from '@/data/sites' const CATEGORY_ICONS = { ai: Brain, 'developer-tools': Code, documentation: BookOpen, cloud: Cloud, other: Layers, } const CATEGORY_COLORS = { ai: 'bg-purple-500/10 text-purple-400 border-purple-500/20', 'developer-tools': 'bg-blue-500/10 text-blue-400 border-blue-500/20', documentation: 'bg-green-500/10 text-green-400 border-green-500/20', cloud: 'bg-orange-500/10 text-orange-400 border-orange-500/20', other: 'bg-neutral-500/10 text-neutral-400 border-neutral-500/20', } interface SiteCardProps { site: SiteEntry index: number verification?: VerificationResult | null onVerify?: (siteId: string) => void } function formatBytes(bytes: number): string { if (bytes < 1024) return `${bytes} B` if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` return `${(bytes / (1024 * 1024)).toFixed(1)} MB` } function formatRelativeTime(dateString: string): string { const date = new Date(dateString) const now = new Date() const diffMs = now.getTime() - date.getTime() const diffMins = Math.floor(diffMs / (1000 * 60)) const diffHours = Math.floor(diffMs / (1000 * 60 * 60)) const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)) if (diffMins < 1) return 'Just now' if (diffMins < 60) return `${diffMins}m ago` if (diffHours < 24) return `${diffHours}h ago` return `${diffDays}d ago` } function StatusIndicator({ verification, isChecking }: { verification?: VerificationResult | null; isChecking?: boolean }) { if (isChecking) { return (
Checking...
) } if (!verification) { return (
Not checked
) } switch (verification.status) { case 'online': return (
Online
) case 'offline': return (
Offline
) case 'error': return (
Error
) default: return (
Unknown
) } } export default function SiteCard({ site, index, verification: initialVerification, onVerify }: SiteCardProps) { const [verification, setVerification] = useState(initialVerification || null) const [isChecking, setIsChecking] = useState(false) const [showDetails, setShowDetails] = useState(false) const CategoryIcon = CATEGORY_ICONS[site.category] const categoryColor = CATEGORY_COLORS[site.category] // Use llmsTxtUrl for extraction if available const extractBaseUrl = site.llmsTxtUrl || site.url const extractUrl = `/extract?url=${encodeURIComponent(extractBaseUrl)}` // Verify on mount if not already verified useEffect(() => { if (!verification && !isChecking) { handleVerify() } }, []) // eslint-disable-line react-hooks/exhaustive-deps const handleVerify = async () => { setIsChecking(true) try { const response = await fetch(`/api/sites/verify?id=${site.id}`) if (response.ok) { const result = await response.json() setVerification(result) } } catch (error) { console.error('Verification failed:', error) } finally { setIsChecking(false) } onVerify?.(site.id) } const llmsInfo = verification?.llmsFullTxt || verification?.llmsTxt const hasInstallMd = verification?.installMd?.exists || site.hasInstallMd return ( {/* Header */}
{/* Favicon */}
{/* Use Google's favicon service */} { // Fallback to first letter e.currentTarget.style.display = 'none' e.currentTarget.parentElement!.innerHTML = `${site.name.charAt(0)}` }} />

{site.name}

{new URL(site.url).hostname}
{/* Live Status */}
{/* Description */}

{site.description}

{/* File info (when verified) */} {verification?.status === 'online' && llmsInfo && (
setShowDetails(!showDetails)} > {llmsInfo.type === 'full' ? 'Full' : 'Standard'} {llmsInfo.size && ( {formatBytes(llmsInfo.size)} )} {verification.responseTime && ( {verification.responseTime}ms )} {hasInstallMd && ( + install.md )}
)} {/* Expanded details */} {showDetails && verification?.status === 'online' && ( {llmsInfo?.url && (

URL:{' '} {llmsInfo.url}

)} {verification.checkedAt && (

Checked: {formatRelativeTime(verification.checkedAt)}

)}
)} {/* Category and type badges */}
{site.category === 'developer-tools' ? 'Dev Tools' : site.category.charAt(0).toUpperCase() + site.category.slice(1)} {(llmsInfo?.type || site.llmsTxtType) === 'full' ? 'Full llms.txt' : 'Standard'}
{/* Actions */}
Extract
) }