diff --git a/frontend/src/app/acquire/page.tsx b/frontend/src/app/acquire/page.tsx index 78c533d..f6e6c30 100644 --- a/frontend/src/app/acquire/page.tsx +++ b/frontend/src/app/acquire/page.tsx @@ -187,11 +187,29 @@ export default function AcquirePage() { } } + // Trigger re-render every 30s to update live times + const [tick, setTick] = useState(0) + useEffect(() => { + const interval = setInterval(() => setTick(t => t + 1), 30000) + return () => clearInterval(interval) + }, []) + const displayAuctions = useMemo(() => { const current = getCurrentAuctions() - if (isAuthenticated) return current - return current.filter(isVanityDomain) - }, [activeTab, allAuctions, endingSoon, hotAuctions, isAuthenticated]) + const nowMs = Date.now() + + // Filter out expired auctions + const activeAuctions = current.filter(auction => { + if (!auction.end_time) return true + const endMs = Date.parse(auction.end_time) + if (Number.isNaN(endMs)) return true + return endMs > (nowMs - 2000) // 2s grace + }) + + if (isAuthenticated) return activeAuctions + return activeAuctions.filter(isVanityDomain) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [activeTab, allAuctions, endingSoon, hotAuctions, isAuthenticated, tick]) const filteredAuctions = displayAuctions.filter(auction => { if (searchQuery && !auction.domain.toLowerCase().includes(searchQuery.toLowerCase())) return false diff --git a/frontend/src/app/terminal/portfolio/page.tsx b/frontend/src/app/terminal/portfolio/page.tsx index 72b93ba..6bbc0ba 100755 --- a/frontend/src/app/terminal/portfolio/page.tsx +++ b/frontend/src/app/terminal/portfolio/page.tsx @@ -139,6 +139,7 @@ export default function PortfolioPage() { const [healthReports, setHealthReports] = useState>({}) const [loadingHealth, setLoadingHealth] = useState>({}) const [togglingAlerts, setTogglingAlerts] = useState>({}) + const [alertsEnabled, setAlertsEnabled] = useState>({}) const [showHealthDetail, setShowHealthDetail] = useState(null) const [showYieldModal, setShowYieldModal] = useState(null) @@ -219,18 +220,20 @@ export default function PortfolioPage() { // ALERT HANDLERS // ═══════════════════════════════════════════════════════════════════════════ - const handleToggleEmailAlert = useCallback(async (domainId: number, currentValue: boolean) => { + const handleToggleEmailAlert = useCallback(async (domainId: number, _currentValue: boolean) => { + const currentEnabled = alertsEnabled[domainId] || false setTogglingAlerts(prev => ({ ...prev, [domainId]: true })) try { // This would call a backend endpoint to toggle email alerts - // await api.updatePortfolioDomainAlerts(domainId, { email_alerts: !currentValue }) - showToast(!currentValue ? 'Email alerts enabled' : 'Email alerts disabled', 'success') + // await api.updatePortfolioDomainAlerts(domainId, { email_alerts: !currentEnabled }) + setAlertsEnabled(prev => ({ ...prev, [domainId]: !currentEnabled })) + showToast(!currentEnabled ? 'Alerts enabled' : 'Alerts disabled', 'success') } catch { showToast('Failed to update alert settings', 'error') } finally { setTogglingAlerts(prev => ({ ...prev, [domainId]: false })) } - }, [showToast]) + }, [showToast, alertsEnabled]) const handleToggleSmsAlert = useCallback(async (domainId: number, currentValue: boolean) => { if (!canUseSmsAlerts) { @@ -517,8 +520,8 @@ export default function PortfolioPage() { ) : (
- {/* Desktop Table Header - Extended with Health & Alerts */} -
+ {/* Desktop Table Header - Matches Watchlist Style */} +
- -
Alerts
-
Yield
+
Alert
Actions
@@ -678,205 +676,152 @@ export default function PortfolioPage() {
- {/* Desktop Row - Extended with Health, Alerts, Yield */} -
+ {/* Desktop Row - Matches Watchlist Style */} +
{/* Domain Info */}
{domain.is_sold ? : domain.is_dns_verified ? : }
-
-
- {domain.domain} - - - -
-
- {domain.registrar || '—'} - {domain.is_sold && Sold} - {!domain.is_sold && domain.is_dns_verified && Verified} - {!domain.is_sold && !domain.is_dns_verified && Unverified} +
+
{domain.domain}
+
+ {domain.registrar || 'Unknown'} + {domain.is_sold && • Sold} + {!domain.is_sold && domain.is_dns_verified && • Verified}
+ + + +
+ + {/* Health - Like Watchlist */} + + + {/* Value + ROI combined */} +
+
{formatCurrency(domain.estimated_value)}
+
+ {roiPositive ? '+' : ''}{formatROI(domain.roi)} +
- {/* Health Status - NEW */} -
- {domain.is_sold ? ( - - ) : domain.is_dns_verified ? ( - (() => { - const health = healthReports[domain.id] - const isLoading = loadingHealth[domain.id] - if (isLoading) { - return - } - if (!health) { - return ( - - ) - } - const config = healthConfig[health.status] - return ( - - ) - })() - ) : ( - Verify first - )} -
- - {/* Estimated Value */} -
- {formatCurrency(domain.estimated_value)} -
- - {/* ROI Badge */} -
- - {roiPositive ? : } - {formatROI(domain.roi)} - -
- - {/* Renewal/Expiry */} -
+ {/* Expiry - Like Watchlist */} +
{domain.is_sold ? ( ) : isRenewingSoon ? ( {daysUntilRenewal}d ) : daysUntilRenewal ? ( - {daysUntilRenewal}d + {daysUntilRenewal}d ) : ( - + formatDate(domain.renewal_date) )}
- {/* Alerts - Email & SMS - NEW */} -
- {domain.is_sold ? ( - - ) : ( - <> - - - + {/* Alert Toggle - Like Watchlist Bell */} +
- - {/* Yield Status - Phase 2 - NEW */} -
- {domain.is_sold ? ( - - ) : !canUseYield ? ( - - - - ) : domain.is_dns_verified ? ( - + > + {togglingAlerts[domain.id] ? ( + + ) : alertsEnabled[domain.id] ? ( + ) : ( - + )} -
+ - {/* Actions - Better organized */} -
+ {/* Actions - Like Watchlist */} +
{/* Primary Action - Verify or Sell */} {!domain.is_sold && ( domain.is_dns_verified ? ( canListForSale && ( - - Sell - + className="h-7 px-3 bg-amber-400/10 text-amber-400 text-xs font-bold flex items-center gap-1.5 border border-amber-400/20 hover:bg-amber-400/20 transition-colors" + > + Sell + + ) ) : ( ) )} - - {/* Secondary Actions - Icon Buttons */} -
- + -