fix: TypeScript error in Portfolio health reports
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-14 09:16:09 +01:00
parent 909cf21d6e
commit e027e51288

View File

@ -169,18 +169,18 @@ export default function PortfolioPage() {
api.checkDomain(domain.domain)
.then(() => {
// Simulate health report - in production this would come from backend
setHealthReports(prev => ({
...prev,
[domain.id]: {
domain_id: domain.id,
checked_at: new Date().toISOString(),
score: Math.floor(Math.random() * 40) + 60, // Simulated score 60-100
status: 'healthy' as HealthStatus,
dns: { has_a: true, has_ns: true, is_parked: false },
http: { is_reachable: true, status_code: 200, is_parked: false },
ssl: { has_certificate: true },
} as DomainHealthReport
}))
const simulatedReport: DomainHealthReport = {
domain: domain.domain,
checked_at: new Date().toISOString(),
score: Math.floor(Math.random() * 40) + 60, // Simulated score 60-100
status: 'healthy' as HealthStatus,
signals: [],
recommendations: [],
dns: { has_a: true, has_ns: true, has_mx: false, nameservers: [], is_parked: false },
http: { is_reachable: true, status_code: 200, is_parked: false },
ssl: { has_certificate: true },
}
setHealthReports(prev => ({ ...prev, [domain.id]: simulatedReport }))
})
.catch(() => {})
.finally(() => {
@ -195,18 +195,18 @@ export default function PortfolioPage() {
try {
await api.checkDomain(domainName)
// Simulated - in production, this would return real health data
setHealthReports(prev => ({
...prev,
[domainId]: {
domain_id: domainId,
checked_at: new Date().toISOString(),
score: Math.floor(Math.random() * 40) + 60,
status: 'healthy' as HealthStatus,
dns: { has_a: true, has_ns: true, is_parked: false },
http: { is_reachable: true, status_code: 200, is_parked: false },
ssl: { has_certificate: true },
} as DomainHealthReport
}))
const simulatedReport: DomainHealthReport = {
domain: domainName,
checked_at: new Date().toISOString(),
score: Math.floor(Math.random() * 40) + 60,
status: 'healthy' as HealthStatus,
signals: [],
recommendations: [],
dns: { has_a: true, has_ns: true, has_mx: false, nameservers: [], is_parked: false },
http: { is_reachable: true, status_code: 200, is_parked: false },
ssl: { has_certificate: true },
}
setHealthReports(prev => ({ ...prev, [domainId]: simulatedReport }))
showToast('Health check complete', 'success')
} catch {
showToast('Health check failed', 'error')
@ -388,14 +388,14 @@ export default function PortfolioPage() {
<div className="flex items-center gap-2 text-[10px] font-mono text-white/40">
<span>{stats.total} domains</span>
</div>
</div>
</div>
{/* Stats Grid - Extended with Health */}
<div className="grid grid-cols-5 gap-1.5">
<div className="bg-white/[0.02] border border-white/[0.08] p-2">
<div className="text-base font-bold text-white tabular-nums">{stats.active}</div>
<div className="text-[8px] font-mono text-white/30 uppercase tracking-wider">Active</div>
</div>
</div>
<div className="bg-accent/[0.05] border border-accent/20 p-2">
<div className="text-base font-bold text-accent tabular-nums">{formatCurrency(summary?.total_value || 0).replace('$', '').slice(0, 6)}</div>
<div className="text-[8px] font-mono text-accent/60 uppercase tracking-wider">Value</div>
@ -485,7 +485,7 @@ export default function PortfolioPage() {
{item.label} ({item.count})
</button>
))}
</div>
</div>
{/* Add Domain Button - RIGHT */}
<button
@ -602,14 +602,14 @@ export default function PortfolioPage() {
<Clock className="w-3 h-3 text-white/30" />
<span className={isRenewingSoon ? "text-orange-400 font-bold" : "text-white/40"}>
{isRenewingSoon ? `${daysUntilRenewal}d` : formatDate(domain.renewal_date)}
</span>
</span>
</div>
)}
</div>
)}
</div>
{/* Alert Toggles - Mobile */}
<div className="flex items-center gap-1">
<button
<button
onClick={() => handleToggleEmailAlert(domain.id, false)}
className="w-7 h-7 flex items-center justify-center text-white/30 hover:text-accent border border-white/[0.06]"
title="Email alerts"
@ -619,7 +619,7 @@ export default function PortfolioPage() {
<button
onClick={() => handleToggleSmsAlert(domain.id, false)}
disabled={!canUseSmsAlerts}
className={clsx(
className={clsx(
"w-7 h-7 flex items-center justify-center border border-white/[0.06]",
canUseSmsAlerts ? "text-white/30 hover:text-accent" : "text-white/10"
)}
@ -659,8 +659,8 @@ export default function PortfolioPage() {
className="flex-1 py-2 bg-blue-400/10 border border-blue-400/20 text-blue-400 text-[10px] font-bold uppercase flex items-center justify-center gap-1"
>
<ShieldAlert className="w-3 h-3" />Verify
</button>
)
</button>
)
)}
<button
onClick={() => setSelectedDomain(domain)}
@ -718,19 +718,19 @@ export default function PortfolioPage() {
return <Loader2 className="w-4 h-4 text-white/30 animate-spin" />
}
if (!health) {
return (
<button
return (
<button
onClick={() => handleRefreshHealth(domain.id, domain.domain)}
className="text-white/30 hover:text-white"
title="Run health check"
>
>
<Activity className="w-4 h-4" />
</button>
)
</button>
)
}
const config = healthConfig[health.status]
return (
<button
<button
onClick={() => setShowHealthDetail(domain.id)}
className={clsx("flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-mono border", config.bg, config.color)}
title={`Score: ${health.score}/100`}
@ -739,7 +739,7 @@ export default function PortfolioPage() {
health.status === 'critical' ? <WifiOff className="w-3 h-3" /> :
<AlertCircle className="w-3 h-3" />}
{health.score}
</button>
</button>
)
})()
) : (
@ -782,15 +782,15 @@ export default function PortfolioPage() {
<span className="text-white/20"></span>
) : (
<>
<button
<button
onClick={() => handleToggleEmailAlert(domain.id, false)}
disabled={togglingAlerts[domain.id]}
className="w-6 h-6 flex items-center justify-center text-white/30 hover:text-accent border border-transparent hover:border-accent/20 transition-all"
title="Email alerts"
>
<Mail className="w-3 h-3" />
</button>
<button
</button>
<button
onClick={() => handleToggleSmsAlert(domain.id, false)}
disabled={togglingAlerts[domain.id] || !canUseSmsAlerts}
className={clsx(
@ -802,7 +802,7 @@ export default function PortfolioPage() {
title={canUseSmsAlerts ? "SMS alerts" : "SMS alerts require Tycoon"}
>
{canUseSmsAlerts ? <Smartphone className="w-3 h-3" /> : <Lock className="w-3 h-3" />}
</button>
</button>
</>
)}
</div>
@ -834,12 +834,12 @@ export default function PortfolioPage() {
{!domain.is_sold && (
domain.is_dns_verified ? (
canListForSale && (
<Link
<Link
href={`/terminal/listing?domain=${encodeURIComponent(domain.domain)}`}
className="h-7 px-2.5 flex items-center gap-1.5 text-amber-400 text-[10px] font-bold uppercase tracking-wide border border-amber-400/30 bg-amber-400/10 hover:bg-amber-400/20 transition-all"
>
>
<Tag className="w-3 h-3" />Sell
</Link>
</Link>
)
) : (
<button
@ -853,14 +853,14 @@ export default function PortfolioPage() {
{/* Secondary Actions - Icon Buttons */}
<div className="flex items-center gap-0.5 ml-1">
<button
<button
onClick={() => setSelectedDomain(domain)}
title="Edit Details"
className="w-7 h-7 flex items-center justify-center text-white/30 hover:text-white border border-transparent hover:border-white/10 hover:bg-white/5 transition-all rounded-sm"
>
>
<Edit3 className="w-3.5 h-3.5" />
</button>
<button
</button>
<button
onClick={() => handleRefreshValue(domain.id)}
disabled={refreshingId === domain.id}
title="Refresh Valuation"
@ -875,8 +875,8 @@ export default function PortfolioPage() {
className="w-7 h-7 flex items-center justify-center text-white/30 hover:text-rose-400 border border-transparent hover:border-rose-400/20 hover:bg-rose-500/10 transition-all rounded-sm disabled:opacity-30"
>
{deletingId === domain.id ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Trash2 className="w-3.5 h-3.5" />}
</button>
</div>
</button>
</div>
</div>
</div>
</div>
@ -900,7 +900,7 @@ export default function PortfolioPage() {
<button onClick={() => setMenuOpen(true)} className="flex-1 flex flex-col items-center justify-center gap-0.5 text-white/40">
<Menu className="w-5 h-5" /><span className="text-[9px] font-mono uppercase tracking-wider">Menu</span>
</button>
</div>
</div>
</nav>
{/* MOBILE DRAWER */}
@ -930,7 +930,7 @@ export default function PortfolioPage() {
<div className="flex items-center gap-2">
<Activity className="w-4 h-4 text-accent" />
<span className="text-xs font-mono text-accent uppercase tracking-wider">Health Report</span>
</div>
</div>
<button onClick={() => setShowHealthDetail(null)} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-white">
<X className="w-4 h-4" />
</button>
@ -943,8 +943,8 @@ export default function PortfolioPage() {
<div className={clsx("flex items-center gap-2 px-3 py-1.5 border", config.bg)}>
<span className={clsx("text-lg font-bold font-mono", config.color)}>{health.score}</span>
<span className={clsx("text-[10px] font-mono uppercase", config.color)}>{config.label}</span>
</div>
</div>
</div>
</div>
{/* Health Checks */}
<div className="space-y-2">
@ -954,7 +954,7 @@ export default function PortfolioPage() {
<div className="flex items-center gap-2">
<Globe className="w-4 h-4 text-white/40" />
<span className="text-sm text-white/70">DNS Resolution</span>
</div>
</div>
{health.dns?.has_a || health.dns?.has_ns ? (
<span className="text-accent text-[10px] font-mono uppercase">OK</span>
) : (
@ -1002,12 +1002,12 @@ export default function PortfolioPage() {
{/* Last Check */}
<div className="flex items-center justify-between text-[10px] font-mono text-white/30">
<span>Last checked: {formatTimeAgo(health.checked_at)}</span>
<button
<button
onClick={() => { handleRefreshHealth(domain.id, domain.domain); setShowHealthDetail(null) }}
className="text-accent hover:underline"
>
>
Refresh
</button>
</button>
</div>
</div>
</div>
@ -1046,7 +1046,7 @@ export default function PortfolioPage() {
<div className="flex items-start gap-3">
<div className="w-6 h-6 bg-accent/10 flex items-center justify-center text-accent text-xs font-bold shrink-0">1</div>
<div className="text-sm text-white/70">Point your nameservers to ns.pounce.ch</div>
</div>
</div>
<div className="flex items-start gap-3">
<div className="w-6 h-6 bg-accent/10 flex items-center justify-center text-accent text-xs font-bold shrink-0">2</div>
<div className="text-sm text-white/70">We analyze visitor intent and route traffic</div>
@ -1058,20 +1058,20 @@ export default function PortfolioPage() {
</div>
<div className="pt-2">
<button
<button
onClick={() => setShowYieldModal(null)}
className="w-full py-3 bg-white/5 border border-white/10 text-white/40 text-sm font-mono"
>
Notify me when available
</button>
</div>
</button>
</div>
</div>
</div>
</div>
)}
{toast && <Toast message={toast.message} type={toast.type} onClose={hideToast} />}
</div>
</div>
)
}
@ -1121,18 +1121,18 @@ function AddDomainModal({ onClose, onSuccess }: { onClose: () => void; onSuccess
<form onSubmit={handleSubmit} className="p-4 space-y-4">
{error && <div className="p-2 bg-rose-500/10 border border-rose-500/20 text-rose-400 text-xs">{error}</div>}
<div>
<div>
<label className="block text-[9px] font-mono text-white/40 uppercase mb-1.5">Domain *</label>
<input type="text" value={domain} onChange={(e) => setDomain(e.target.value)} required
className="w-full px-3 py-2.5 bg-white/5 border border-white/10 text-white text-sm font-mono placeholder:text-white/20 outline-none focus:border-accent/50" placeholder="example.com" />
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<div>
<label className="block text-[9px] font-mono text-white/40 uppercase mb-1.5">Purchase Price (USD)</label>
<input type="number" value={purchasePrice} onChange={(e) => setPurchasePrice(e.target.value)} min="0"
className="w-full px-3 py-2.5 bg-white/5 border border-white/10 text-white text-sm font-mono placeholder:text-white/20 outline-none focus:border-accent/50" placeholder="0" />
</div>
</div>
<div>
<label className="block text-[9px] font-mono text-white/40 uppercase mb-1.5">Purchase Date</label>
<input type="date" value={purchaseDate} onChange={(e) => setPurchaseDate(e.target.value)}
@ -1163,9 +1163,9 @@ function AddDomainModal({ onClose, onSuccess }: { onClose: () => void; onSuccess
<button type="button" onClick={onClose} className="flex-1 py-2.5 border border-white/10 text-white/60 text-xs font-mono uppercase">Cancel</button>
<button type="submit" disabled={loading || !domain.trim()} className="flex-1 flex items-center justify-center gap-2 py-2.5 bg-accent text-black text-xs font-bold uppercase disabled:opacity-50">
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Plus className="w-4 h-4" />}Add
</button>
</div>
</form>
</button>
</div>
</form>
</div>
</div>
)
@ -1205,30 +1205,30 @@ function DomainDetailModal({ domain, onClose, onUpdate, canListForSale }: { doma
<div className="flex items-center justify-between p-4 border-b border-white/[0.08]">
<div className="flex items-center gap-2"><BarChart3 className="w-4 h-4 text-accent" /><span className="text-xs font-mono text-accent uppercase tracking-wider">Domain Details</span></div>
<button onClick={onClose} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40"><X className="w-4 h-4" /></button>
</div>
</div>
<div className="p-4 space-y-4">
{/* Domain Header */}
<div className="text-center py-4 border-b border-white/[0.08]">
<h2 className="text-xl font-bold font-mono text-white">{domain.domain}</h2>
<p className="text-xs font-mono text-white/40 mt-1">{domain.registrar || 'Unknown registrar'}</p>
</div>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-3 gap-3">
<div className="p-3 bg-white/[0.02] border border-white/[0.08] text-center">
<div className="text-lg font-bold text-white font-mono">{formatCurrency(domain.purchase_price)}</div>
<div className="text-[9px] font-mono text-white/30 uppercase">Purchased</div>
</div>
</div>
<div className="p-3 bg-accent/[0.05] border border-accent/20 text-center">
<div className="text-lg font-bold text-accent font-mono">{formatCurrency(domain.estimated_value)}</div>
<div className="text-[9px] font-mono text-accent/60 uppercase">Est. Value</div>
</div>
</div>
<div className={clsx("p-3 border text-center", (domain.roi || 0) >= 0 ? "bg-accent/[0.05] border-accent/20" : "bg-rose-500/[0.05] border-rose-500/20")}>
<div className={clsx("text-lg font-bold font-mono", (domain.roi || 0) >= 0 ? "text-accent" : "text-rose-400")}>{formatROI(domain.roi)}</div>
<div className="text-[9px] font-mono text-white/30 uppercase">ROI</div>
</div>
</div>
</div>
{/* Dates */}
<div className="grid grid-cols-2 gap-3">
@ -1310,13 +1310,13 @@ function SellModal({ onClose, onConfirm }: { onClose: () => void; onConfirm: (da
<label className="block text-[9px] font-mono text-white/40 uppercase mb-1.5">Sale Date</label>
<input type="date" value={saleDate} onChange={(e) => setSaleDate(e.target.value)}
className="w-full px-3 py-2.5 bg-white/5 border border-white/10 text-white text-sm font-mono outline-none focus:border-accent/50" />
</div>
<div>
</div>
<div>
<label className="block text-[9px] font-mono text-white/40 uppercase mb-1.5">Sale Price (USD) *</label>
<input type="number" value={salePrice} onChange={(e) => setSalePrice(e.target.value)} min="0" required
className="w-full px-3 py-2.5 bg-white/5 border border-white/10 text-white text-sm font-mono outline-none focus:border-accent/50" placeholder="0" />
</div>
</div>
</div>
<div className="flex gap-3">
<button onClick={onClose} className="flex-1 py-2.5 border border-white/10 text-white/60 text-xs font-mono uppercase">Cancel</button>
<button onClick={() => onConfirm(saleDate, parseFloat(salePrice) || 0)} disabled={!salePrice} className="flex-1 py-2.5 bg-accent text-black text-xs font-bold uppercase disabled:opacity-50">Confirm</button>
@ -1336,12 +1336,12 @@ function MobileDrawer({ user, tierName, TierIcon, sections, onClose, onLogout }:
<div className="absolute inset-0 bg-black/80" onClick={onClose} />
<div className="absolute top-0 right-0 bottom-0 w-[80%] max-w-[300px] bg-[#0A0A0A] border-l border-white/[0.08] flex flex-col" style={{ paddingTop: 'env(safe-area-inset-top)', paddingBottom: 'env(safe-area-inset-bottom)' }}>
<div className="flex items-center justify-between p-4 border-b border-white/[0.08]">
<div className="flex items-center gap-3">
<div className="flex items-center gap-3">
<Image src="/pounce-puma.png" alt="Pounce" width={28} height={28} className="object-contain" />
<div><h2 className="text-sm font-bold text-white">POUNCE</h2><p className="text-[9px] text-white/40 font-mono uppercase">Terminal v1.0</p></div>
</div>
</div>
<button onClick={onClose} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/60"><X className="w-4 h-4" /></button>
</div>
</div>
<div className="flex-1 overflow-y-auto py-4">
{sections.map((section: any) => (
<div key={section.title} className="mb-4">
@ -1352,23 +1352,23 @@ function MobileDrawer({ user, tierName, TierIcon, sections, onClose, onLogout }:
{item.isNew && <span className="px-1.5 py-0.5 text-[8px] font-bold bg-accent text-black">NEW</span>}
</Link>
))}
</div>
</div>
))}
<div className="pt-3 border-t border-white/[0.08] mx-4">
<Link href="/terminal/settings" onClick={onClose} className="flex items-center gap-3 py-2.5 text-white/50"><Settings className="w-4 h-4" /><span className="text-sm">Settings</span></Link>
{user?.is_admin && <Link href="/admin" onClick={onClose} className="flex items-center gap-3 py-2.5 text-amber-500/70"><Shield className="w-4 h-4" /><span className="text-sm">Admin</span></Link>}
</div>
</div>
</div>
<div className="p-4 bg-white/[0.02] border-t border-white/[0.08]">
<div className="flex items-center gap-3 mb-3">
<div className="w-8 h-8 bg-accent/10 border border-accent/20 flex items-center justify-center"><TierIcon className="w-4 h-4 text-accent" /></div>
<div className="flex-1 min-w-0"><p className="text-sm font-bold text-white truncate">{user?.name || user?.email?.split('@')[0] || 'User'}</p><p className="text-[9px] font-mono text-white/40 uppercase">{tierName}</p></div>
</div>
</div>
{tierName === 'Scout' && <Link href="/pricing" onClick={onClose} className="flex items-center justify-center gap-2 w-full py-2.5 bg-accent text-black text-xs font-bold uppercase mb-2"><Sparkles className="w-3 h-3" />Upgrade</Link>}
<button onClick={onLogout} className="flex items-center justify-center gap-2 w-full py-2 border border-white/10 text-white/40 text-[10px] font-mono uppercase"><LogOut className="w-3 h-3" />Sign out</button>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
@ -1438,18 +1438,18 @@ function DnsVerificationModal({ domain, onClose, onSuccess }: { domain: Portfoli
<div className="flex items-center gap-2">
<ShieldCheck className="w-4 h-4 text-blue-400" />
<span className="text-xs font-mono text-blue-400 uppercase tracking-wider">Verify Domain Ownership</span>
</div>
</div>
<button onClick={onClose} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40">
<X className="w-4 h-4" />
</button>
</div>
</div>
<div className="p-4 space-y-4">
{/* Domain Header */}
<div className="text-center py-3 border-b border-white/[0.08]">
<h2 className="text-xl font-bold font-mono text-white">{domain.domain}</h2>
<p className="text-xs font-mono text-white/40 mt-1">DNS Verification Required</p>
</div>
</div>
{step === 'loading' && (
<div className="flex items-center justify-center py-8">
@ -1462,20 +1462,20 @@ function DnsVerificationModal({ domain, onClose, onSuccess }: { domain: Portfoli
{/* Instructions */}
<div className="p-4 bg-blue-400/5 border border-blue-400/20">
<h3 className="text-sm font-bold text-white mb-2">Add this TXT record to your DNS:</h3>
<div className="space-y-3">
<div>
<div className="space-y-3">
<div>
<div className="text-[9px] font-mono text-white/40 uppercase mb-1">Host / Name</div>
<div className="flex items-center gap-2">
<code className="flex-1 px-3 py-2 bg-black/50 text-sm font-mono text-white break-all">_pounce</code>
<button onClick={() => handleCopy('_pounce')} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-white">
{copied ? <Check className="w-4 h-4 text-accent" /> : <Copy className="w-4 h-4" />}
</button>
</div>
</div>
</div>
<div>
<div>
<div className="text-[9px] font-mono text-white/40 uppercase mb-1">Type</div>
<code className="block px-3 py-2 bg-black/50 text-sm font-mono text-white">TXT</code>
</div>
</div>
<div>
<div className="text-[9px] font-mono text-white/40 uppercase mb-1">Value</div>
<div className="flex items-center gap-2">
@ -1483,26 +1483,26 @@ function DnsVerificationModal({ domain, onClose, onSuccess }: { domain: Portfoli
<button onClick={() => handleCopy(verificationData.verification_code)} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-white">
{copied ? <Check className="w-4 h-4 text-accent" /> : <Copy className="w-4 h-4" />}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Info */}
<div className="p-3 bg-white/[0.02] border border-white/[0.08] text-xs text-white/50 font-mono">
<p>DNS changes can take up to 48 hours to propagate, but usually complete within minutes.</p>
</div>
</div>
{/* Error/Check Result */}
{error && (
<div className="p-3 bg-rose-500/10 border border-rose-500/20 text-rose-400 text-xs font-mono">
{error}
</div>
</div>
)}
{checkResult && (
<div className="p-3 bg-amber-400/10 border border-amber-400/20 text-amber-400 text-xs font-mono">
{checkResult}
</div>
</div>
)}
{/* Actions */}
@ -1512,8 +1512,8 @@ function DnsVerificationModal({ domain, onClose, onSuccess }: { domain: Portfoli
</button>
<button onClick={handleCheck} className="flex-1 py-2.5 bg-blue-400 text-black text-xs font-bold uppercase flex items-center justify-center gap-2">
<RefreshCw className="w-4 h-4" />Check Verification
</button>
</div>
</button>
</div>
</>
)}
@ -1521,7 +1521,7 @@ function DnsVerificationModal({ domain, onClose, onSuccess }: { domain: Portfoli
<div className="flex flex-col items-center justify-center py-8 gap-3">
<Loader2 className="w-6 h-6 text-blue-400 animate-spin" />
<p className="text-sm font-mono text-white/60">Checking DNS records...</p>
</div>
</div>
)}
{step === 'instructions' && !verificationData && error && (