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
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:
@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user