Radar search: track taken domains, Watchlist: radar style cards, fixed health check
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled

This commit is contained in:
2025-12-12 22:08:07 +01:00
parent 7b0b6a3669
commit e737de6ff5
2 changed files with 241 additions and 268 deletions

View File

@ -295,17 +295,29 @@ export default function RadarPage() {
</span>
</div>
{/* Available Actions */}
{searchResult.is_available && (
<div className="flex gap-3">
<button
onClick={handleAddToWatchlist}
disabled={addingToWatchlist}
className="flex-1 py-3 border border-white/20 text-white/80 text-sm font-medium hover:bg-white/5 transition-colors flex items-center justify-center gap-2"
>
{addingToWatchlist ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
Add to Watchlist
</button>
{/* Registrar Info */}
{!searchResult.is_available && searchResult.registrar && (
<p className="text-xs text-white/30 mb-4">
Registered with {searchResult.registrar}
</p>
)}
{/* Actions - Always show */}
<div className="flex gap-3">
<button
onClick={handleAddToWatchlist}
disabled={addingToWatchlist}
className={clsx(
"py-3 text-sm font-medium transition-colors flex items-center justify-center gap-2",
searchResult.is_available
? "flex-1 border border-white/20 text-white/80 hover:bg-white/5"
: "flex-1 border border-accent/30 text-accent hover:bg-accent/10"
)}
>
{addingToWatchlist ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
{searchResult.is_available ? 'Add to Watchlist' : 'Track for Availability'}
</button>
{searchResult.is_available && (
<a
href={`https://www.namecheap.com/domains/registration/results/?domain=${searchResult.domain}`}
target="_blank"
@ -313,15 +325,8 @@ export default function RadarPage() {
>
Register Now <ArrowRight className="w-4 h-4" />
</a>
</div>
)}
{/* Taken Info */}
{!searchResult.is_available && searchResult.registrar && (
<p className="text-xs text-white/30 mt-2">
Registered with {searchResult.registrar}
</p>
)}
)}
</div>
</div>
)}
</div>

View File

@ -15,19 +15,14 @@ import {
Eye,
X,
Activity,
AlertTriangle,
ArrowRight,
CheckCircle2,
XCircle,
Target,
Globe,
ExternalLink,
Calendar,
Shield,
Crosshair
} from 'lucide-react'
import clsx from 'clsx'
import Link from 'next/link'
// ============================================================================
// HELPERS
@ -46,26 +41,12 @@ function formatExpiryDate(expirationDate: string | null): string {
return new Date(expirationDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
}
function getTimeAgo(date: string | null): string {
if (!date) return 'Never'
const now = new Date()
const past = new Date(date)
const diffMs = now.getTime() - past.getTime()
const diffMins = Math.floor(diffMs / 60000)
const diffHours = Math.floor(diffMs / 3600000)
const diffDays = Math.floor(diffMs / 86400000)
if (diffMins < 1) return 'Just now'
if (diffMins < 60) return `${diffMins}m ago`
if (diffHours < 24) return `${diffHours}h ago`
return `${diffDays}d ago`
}
const healthConfig: Record<HealthStatus, { label: string; color: string; bg: string }> = {
healthy: { label: 'Healthy', color: 'text-accent', bg: 'bg-accent/10 border-accent/20' },
weakening: { label: 'Weak', color: 'text-amber-400', bg: 'bg-amber-500/10 border-amber-500/20' },
parked: { label: 'Parked', color: 'text-blue-400', bg: 'bg-blue-500/10 border-blue-500/20' },
critical: { label: 'Critical', color: 'text-rose-400', bg: 'bg-rose-500/10 border-rose-500/20' },
unknown: { label: 'Unknown', color: 'text-white/40', bg: 'bg-white/5 border-white/10' },
const healthConfig: Record<HealthStatus, { label: string; color: string; bgClass: string }> = {
healthy: { label: 'Healthy', color: 'text-accent', bgClass: 'bg-accent/10' },
weakening: { label: 'Weak', color: 'text-amber-400', bgClass: 'bg-amber-500/10' },
parked: { label: 'Parked', color: 'text-blue-400', bgClass: 'bg-blue-500/10' },
critical: { label: 'Critical', color: 'text-rose-400', bgClass: 'bg-rose-500/10' },
unknown: { label: 'Unknown', color: 'text-white/40', bgClass: 'bg-white/5' },
}
// ============================================================================
@ -121,10 +102,10 @@ export default function WatchlistPage() {
setAdding(true)
try {
await addDomain(newDomain.trim())
showToast(`Target locked: ${newDomain.trim()}`, 'success')
showToast(`Added ${newDomain.trim()} to watchlist`, 'success')
setNewDomain('')
} catch (err: any) {
showToast(err.message || 'Failed', 'error')
showToast(err.message || 'Failed to add domain', 'error')
} finally {
setAdding(false)
}
@ -134,18 +115,18 @@ export default function WatchlistPage() {
setRefreshingId(id)
try {
await refreshDomain(id)
showToast('Intel updated', 'success')
} catch { showToast('Update failed', 'error') }
showToast('Domain refreshed', 'success')
} catch { showToast('Refresh failed', 'error') }
finally { setRefreshingId(null) }
}, [refreshDomain, showToast])
const handleDelete = useCallback(async (id: number, name: string) => {
if (!confirm(`Drop target: ${name}?`)) return
if (!confirm(`Remove ${name} from watchlist?`)) return
setDeletingId(id)
try {
await deleteDomain(id)
showToast('Target dropped', 'success')
} catch { showToast('Failed', 'error') }
showToast('Domain removed', 'success')
} catch { showToast('Failed to remove', 'error') }
finally { setDeletingId(null) }
}, [deleteDomain, showToast])
@ -154,8 +135,8 @@ export default function WatchlistPage() {
try {
await api.updateDomainNotify(id, !current)
updateDomain(id, { notify_on_available: !current })
showToast(!current ? 'Alerts armed' : 'Alerts disarmed', 'success')
} catch { showToast('Failed', 'error') }
showToast(!current ? 'Notifications enabled' : 'Notifications disabled', 'success')
} catch { showToast('Failed to update', 'error') }
finally { setTogglingNotifyId(null) }
}, [updateDomain, showToast])
@ -165,11 +146,13 @@ export default function WatchlistPage() {
try {
const report = await api.getDomainHealth(id, { refresh: true })
setHealthReports(prev => ({ ...prev, [id]: report }))
} catch {}
} catch (err) {
console.error('Health check failed:', err)
}
finally { setLoadingHealth(prev => ({ ...prev, [id]: false })) }
}, [loadingHealth])
// Load health
// Load health on mount
useEffect(() => {
const load = async () => {
if (!domains?.length) return
@ -190,25 +173,21 @@ export default function WatchlistPage() {
{toast && <Toast message={toast.message} type={toast.type} onClose={hideToast} />}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* HEADER - Compact */}
{/* HEADER */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<section className="pt-6 lg:pt-8 pb-6">
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-6">
{/* Left */}
<div className="space-y-3">
<div className="inline-flex items-center gap-2">
<Target className="w-4 h-4 text-accent" />
<span className="text-[10px] font-mono tracking-wide text-accent">Domain Surveillance</span>
</div>
<h1 className="font-display text-[2rem] lg:text-[2.5rem] leading-[1] tracking-[-0.02em]">
<span className="text-white">Watchlist</span>
<span className="text-white/30 ml-3">{stats.total}</span>
</h1>
</div>
{/* Right: Stats */}
<div className="flex gap-6 lg:gap-8">
<div className="text-right">
<div className="text-xl font-display text-accent">{stats.available}</div>
@ -223,28 +202,41 @@ export default function WatchlistPage() {
</section>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* ADD DOMAIN */}
{/* ADD DOMAIN - Radar Style */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<section className="pb-6">
<form onSubmit={handleAdd} className="relative max-w-xl">
<div className="flex items-center bg-[#050505] border border-white/10 focus-within:border-accent/40 transition-colors">
<input
type="text"
value={newDomain}
onChange={(e) => setNewDomain(e.target.value)}
placeholder="Add domain to watch..."
className="flex-1 bg-transparent px-4 py-3 text-sm text-white placeholder:text-white/25 outline-none"
/>
<button
type="submit"
disabled={adding || !newDomain.trim()}
className="h-full px-5 py-3 bg-accent text-black text-sm font-semibold hover:bg-white transition-colors disabled:opacity-30 flex items-center gap-2"
>
{adding ? <Loader2 className="w-4 h-4 animate-spin" /> : <Plus className="w-4 h-4" />}
<span className="hidden sm:inline">Add</span>
</button>
<div className="relative max-w-xl">
<div className="absolute -inset-2 bg-gradient-to-r from-accent/5 via-transparent to-accent/5 blur-2xl opacity-30" />
<div className="relative bg-[#0A0A0A] border border-white/10 overflow-hidden">
<div className="flex items-center justify-between px-4 py-2 border-b border-white/[0.06] bg-black/40">
<span className="text-[10px] font-mono text-white/40 flex items-center gap-2">
<Plus className="w-3 h-3 text-accent" />
Add Domain
</span>
</div>
<form onSubmit={handleAdd} className="p-4">
<div className="flex items-center gap-3">
<input
type="text"
value={newDomain}
onChange={(e) => setNewDomain(e.target.value)}
placeholder="example.com"
className="flex-1 bg-black/30 border border-white/10 px-4 py-3 text-white placeholder:text-white/20 outline-none focus:border-accent/40 transition-colors"
/>
<button
type="submit"
disabled={adding || !newDomain.trim()}
className="px-6 py-3 bg-accent text-black text-sm font-semibold hover:bg-white transition-colors disabled:opacity-30 flex items-center gap-2"
>
{adding ? <Loader2 className="w-4 h-4 animate-spin" /> : <Plus className="w-4 h-4" />}
Add
</button>
</div>
</form>
</div>
</form>
</div>
</section>
{/* ═══════════════════════════════════════════════════════════════════════ */}
@ -274,30 +266,19 @@ export default function WatchlistPage() {
</section>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* TABLE */}
{/* DOMAIN LIST - Radar Style Cards */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<section className="py-6">
{!filteredDomains.length ? (
<div className="text-center py-16">
<div className="w-14 h-14 mx-auto bg-white/[0.02] border border-white/10 rounded-lg flex items-center justify-center mb-4">
<div className="w-14 h-14 mx-auto bg-white/[0.02] border border-white/10 flex items-center justify-center mb-4">
<Eye className="w-6 h-6 text-white/20" />
</div>
<p className="text-white/40 text-sm">No domains in your watchlist</p>
<p className="text-white/25 text-xs mt-1">Add a domain above to start monitoring</p>
</div>
) : (
<div className="space-y-px">
{/* Table Header */}
<div className="hidden lg:grid grid-cols-[1fr_100px_100px_100px_80px_100px] gap-4 px-4 py-2 text-xs text-white/40 border-b border-white/[0.06]">
<div>Domain</div>
<div>Status</div>
<div>Health</div>
<div>Expires</div>
<div>Alert</div>
<div className="text-right">Actions</div>
</div>
{/* Rows */}
<div className="space-y-3">
{filteredDomains.map((domain) => {
const health = healthReports[domain.id]
const healthStatus = health?.status || 'unknown'
@ -307,125 +288,105 @@ export default function WatchlistPage() {
return (
<div
key={domain.id}
className="group bg-white/[0.01] hover:bg-white/[0.03] border border-white/[0.05] hover:border-white/[0.08] transition-all"
className="relative bg-[#0A0A0A] border border-white/10 overflow-hidden group hover:border-white/20 transition-colors"
>
{/* Mobile */}
<div className="lg:hidden p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-3">
<div className={clsx(
"w-2 h-2 rounded-full",
domain.is_available ? "bg-accent shadow-[0_0_8px_rgba(16,185,129,0.8)]" : "bg-white/20"
)} />
<span className="text-sm text-white font-medium">{domain.name}</span>
</div>
<span className={clsx(
"text-xs px-2 py-0.5",
domain.is_available ? "text-accent bg-accent/10" : "text-white/40 bg-white/5"
)}>
{domain.is_available ? 'Available' : 'Taken'}
</span>
</div>
<div className="flex items-center justify-between text-xs text-white/40">
<span>{formatExpiryDate(domain.expiration_date)}</span>
<div className="flex gap-2">
<button onClick={() => handleRefresh(domain.id)} className="p-1.5 hover:bg-white/5 rounded">
<RefreshCw className={clsx("w-4 h-4", refreshingId === domain.id && "animate-spin")} />
</button>
<button onClick={() => handleDelete(domain.id, domain.name)} className="p-1.5 hover:bg-rose-500/10 hover:text-rose-400 rounded">
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
</div>
{/* Desktop */}
<div className="hidden lg:grid grid-cols-[1fr_100px_100px_100px_80px_100px] gap-4 items-center px-4 py-3">
{/* Domain */}
<div className="flex items-center gap-3 min-w-0">
{/* Card Header */}
<div className="flex items-center justify-between px-4 py-2 border-b border-white/[0.06] bg-black/40">
<div className="flex items-center gap-3">
<div className={clsx(
"w-2 h-2 rounded-full shrink-0",
domain.is_available ? "bg-accent shadow-[0_0_6px_rgba(16,185,129,0.8)]" : "bg-white/20"
"w-2 h-2 rounded-full",
domain.is_available ? "bg-accent shadow-[0_0_8px_rgba(16,185,129,0.8)]" : "bg-white/20"
)} />
<span className="text-sm text-white font-medium truncate">{domain.name}</span>
<span className="text-sm font-medium text-white">{domain.name}</span>
<a href={`https://${domain.name}`} target="_blank" className="opacity-0 group-hover:opacity-50 hover:!opacity-100 transition-opacity">
<ExternalLink className="w-3.5 h-3.5" />
<ExternalLink className="w-3.5 h-3.5 text-white/50" />
</a>
</div>
{/* Status */}
<div>
<span className={clsx(
"text-xs px-2 py-0.5",
domain.is_available ? "text-accent bg-accent/10" : "text-white/40 bg-white/5"
)}>
{domain.is_available ? 'Available' : 'Taken'}
</span>
</div>
{/* Health */}
<button
onClick={() => { setSelectedDomain(domain.id); handleHealthCheck(domain.id) }}
className="flex items-center gap-1.5 hover:opacity-80 transition-opacity"
>
{loadingHealth[domain.id] ? (
<Loader2 className="w-3.5 h-3.5 animate-spin text-white/30" />
) : (
<>
<Activity className={clsx("w-3.5 h-3.5", config.color)} />
<span className={clsx("text-xs", config.color)}>{config.label}</span>
</>
)}
</button>
{/* Expires */}
<div className="text-xs text-white/50">
{days !== null && days <= 30 && days > 0 ? (
<span className="text-amber-400 font-medium">{days} days</span>
) : (
formatExpiryDate(domain.expiration_date)
)}
</div>
{/* Alert */}
<button
onClick={() => handleToggleNotify(domain.id, domain.notify_on_available)}
disabled={togglingNotifyId === domain.id}
className={clsx(
"w-6 h-6 flex items-center justify-center transition-colors",
domain.notify_on_available ? "text-accent" : "text-white/20 hover:text-white/40"
)}
>
{togglingNotifyId === domain.id ? (
<Loader2 className="w-3.5 h-3.5 animate-spin" />
) : domain.notify_on_available ? (
<Bell className="w-3.5 h-3.5" />
) : (
<BellOff className="w-3.5 h-3.5" />
)}
</button>
{/* Actions */}
<div className="flex items-center justify-end gap-1">
<button
onClick={() => handleRefresh(domain.id)}
disabled={refreshingId === domain.id}
className="w-7 h-7 flex items-center justify-center text-white/20 hover:text-white hover:bg-white/5 transition-all"
>
<RefreshCw className={clsx("w-3.5 h-3.5", refreshingId === domain.id && "animate-spin")} />
</button>
<button
onClick={() => handleDelete(domain.id, domain.name)}
disabled={deletingId === domain.id}
className="w-7 h-7 flex items-center justify-center text-white/20 hover:text-rose-400 hover:bg-rose-500/10 transition-all"
>
{deletingId === domain.id ? (
<Loader2 className="w-3.5 h-3.5 animate-spin" />
) : (
<Trash2 className="w-3.5 h-3.5" />
)}
</button>
<span className={clsx(
"text-xs px-2 py-0.5",
domain.is_available ? "text-accent bg-accent/10" : "text-white/40 bg-white/5"
)}>
{domain.is_available ? 'Available' : 'Taken'}
</span>
</div>
{/* Card Body */}
<div className="p-4">
<div className="flex items-center justify-between">
{/* Left: Info */}
<div className="flex items-center gap-6">
{/* Health */}
<button
onClick={() => { setSelectedDomain(domain.id); handleHealthCheck(domain.id) }}
className="flex items-center gap-2 hover:opacity-80 transition-opacity"
>
{loadingHealth[domain.id] ? (
<Loader2 className="w-4 h-4 animate-spin text-white/30" />
) : (
<>
<Activity className={clsx("w-4 h-4", config.color)} />
<span className={clsx("text-xs", config.color)}>{config.label}</span>
</>
)}
</button>
{/* Expiry */}
<div className="text-xs text-white/50">
{days !== null && days <= 30 && days > 0 ? (
<span className="text-amber-400 font-medium">{days} days left</span>
) : domain.expiration_date ? (
<span>Expires {formatExpiryDate(domain.expiration_date)}</span>
) : (
<span className="text-white/30">No expiry data</span>
)}
</div>
</div>
{/* Right: Actions */}
<div className="flex items-center gap-2">
{/* Notify Toggle */}
<button
onClick={() => handleToggleNotify(domain.id, domain.notify_on_available)}
disabled={togglingNotifyId === domain.id}
className={clsx(
"w-8 h-8 flex items-center justify-center rounded hover:bg-white/5 transition-all",
domain.notify_on_available ? "text-accent" : "text-white/25 hover:text-white/50"
)}
title={domain.notify_on_available ? 'Notifications on' : 'Notifications off'}
>
{togglingNotifyId === domain.id ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : domain.notify_on_available ? (
<Bell className="w-4 h-4" />
) : (
<BellOff className="w-4 h-4" />
)}
</button>
{/* Refresh */}
<button
onClick={() => handleRefresh(domain.id)}
disabled={refreshingId === domain.id}
className="w-8 h-8 flex items-center justify-center text-white/30 hover:text-white hover:bg-white/5 rounded transition-all"
title="Refresh"
>
<RefreshCw className={clsx("w-4 h-4", refreshingId === domain.id && "animate-spin")} />
</button>
{/* Delete */}
<button
onClick={() => handleDelete(domain.id, domain.name)}
disabled={deletingId === domain.id}
className="w-8 h-8 flex items-center justify-center text-white/30 hover:text-rose-400 hover:bg-rose-500/10 rounded transition-all"
title="Remove"
>
{deletingId === domain.id ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Trash2 className="w-4 h-4" />
)}
</button>
</div>
</div>
</div>
</div>
@ -436,79 +397,86 @@ export default function WatchlistPage() {
</section>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* HEALTH MODAL */}
{/* HEALTH MODAL - Radar Style */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{selectedDomainData && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm" onClick={() => setSelectedDomain(null)}>
<div className="w-full max-w-md bg-[#0A0A0A] border border-white/15 p-1.5" onClick={(e) => e.stopPropagation()}>
{/* Corner Decorations */}
<div className="absolute -top-px -left-px w-4 h-4 border-t border-l border-accent/60" />
<div className="absolute -top-px -right-px w-4 h-4 border-t border-r border-accent/60" />
<div className="absolute -bottom-px -left-px w-4 h-4 border-b border-l border-accent/60" />
<div className="absolute -bottom-px -right-px w-4 h-4 border-b border-r border-accent/60" />
<div className="relative w-full max-w-md" onClick={(e) => e.stopPropagation()}>
<div className="absolute -inset-4 bg-gradient-to-tr from-accent/10 via-transparent to-accent/5 blur-3xl opacity-30" />
<div className="bg-[#050505] p-6 relative">
<div className="relative bg-[#0A0A0A] border border-white/10 overflow-hidden">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<Activity className="w-4 h-4 text-accent" />
<span className="text-sm font-medium text-white">Health Report</span>
</div>
<div className="flex items-center justify-between px-5 py-3 border-b border-white/[0.06] bg-black/40">
<span className="text-[10px] font-mono text-white/40 flex items-center gap-2">
<Activity className="w-3 h-3 text-accent" />
Health Report
</span>
<button onClick={() => setSelectedDomain(null)} className="text-white/30 hover:text-white p-1">
<X className="w-5 h-5" />
<X className="w-4 h-4" />
</button>
</div>
{/* Domain */}
<div className="mb-6">
<h3 className="text-lg font-medium text-white">{selectedDomainData.name}</h3>
<div className="flex items-center gap-2 mt-2">
<span className={clsx(
"text-xs px-2.5 py-1",
healthConfig[selectedHealth?.status || 'unknown'].bg,
healthConfig[selectedHealth?.status || 'unknown'].color
)}>
{healthConfig[selectedHealth?.status || 'unknown'].label}
</span>
<div className="p-5">
{/* Domain */}
<div className="mb-5">
<h3 className="text-lg font-medium text-white">{selectedDomainData.name}</h3>
<div className="flex items-center gap-2 mt-2">
<span className={clsx(
"text-xs px-2.5 py-1",
healthConfig[selectedHealth?.status || 'unknown'].bgClass,
healthConfig[selectedHealth?.status || 'unknown'].color
)}>
{healthConfig[selectedHealth?.status || 'unknown'].label}
</span>
{selectedHealth?.score !== undefined && (
<span className="text-xs text-white/40">Score: {selectedHealth.score}/100</span>
)}
</div>
</div>
</div>
{/* Checks */}
{selectedHealth && (
<div className="space-y-1">
{[
{ label: 'DNS Resolution', value: selectedHealth.dns?.has_a },
{ label: 'HTTP Reachable', value: selectedHealth.http?.is_reachable },
{ label: 'SSL Certificate', value: selectedHealth.ssl?.has_certificate },
{ label: 'Not Parked', value: !selectedHealth.dns?.is_parked && !selectedHealth.http?.is_parked },
].map((check) => (
<div key={check.label} className="flex items-center justify-between py-2.5 border-b border-white/[0.05]">
<span className="text-sm text-white/60">{check.label}</span>
{check.value ? (
<CheckCircle2 className="w-5 h-5 text-accent" />
) : (
<XCircle className="w-5 h-5 text-rose-400" />
)}
</div>
))}
</div>
)}
{/* Refresh */}
<button
onClick={() => handleHealthCheck(selectedDomainData.id)}
disabled={loadingHealth[selectedDomainData.id]}
className="w-full mt-6 py-3 bg-white/5 border border-white/10 text-white/70 text-sm font-medium hover:bg-white/10 hover:text-white transition-all flex items-center justify-center gap-2"
>
{loadingHealth[selectedDomainData.id] ? (
<Loader2 className="w-4 h-4 animate-spin" />
{/* Checks */}
{selectedHealth ? (
<div className="space-y-1 mb-5">
{[
{ label: 'DNS Resolution', value: selectedHealth.dns?.has_a ?? selectedHealth.dns?.has_ns },
{ label: 'HTTP Reachable', value: selectedHealth.http?.is_reachable },
{ label: 'SSL Certificate', value: selectedHealth.ssl?.has_certificate },
{ label: 'Not Parked', value: !(selectedHealth.dns?.is_parked || selectedHealth.http?.is_parked) },
].map((check) => (
<div key={check.label} className="flex items-center justify-between py-2.5 border-b border-white/[0.05]">
<span className="text-sm text-white/60">{check.label}</span>
{check.value ? (
<CheckCircle2 className="w-5 h-5 text-accent" />
) : check.value === false ? (
<XCircle className="w-5 h-5 text-rose-400" />
) : (
<span className="text-xs text-white/30">Unknown</span>
)}
</div>
))}
</div>
) : (
<>
<RefreshCw className="w-4 h-4" />
Refresh Health Check
</>
<div className="py-6 text-center text-white/30 text-sm">
No health data available yet
</div>
)}
</button>
{/* Refresh Button */}
<button
onClick={() => handleHealthCheck(selectedDomainData.id)}
disabled={loadingHealth[selectedDomainData.id]}
className="w-full py-3 bg-accent text-black text-sm font-semibold hover:bg-white transition-colors flex items-center justify-center gap-2"
>
{loadingHealth[selectedDomainData.id] ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<>
<RefreshCw className="w-4 h-4" />
Run Health Check
</>
)}
</button>
</div>
</div>
</div>
</div>