'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 (
)
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 */}
)
}