fix: Consistent domain status across pages + refresh-all timezone fix
Backend: - Fixed datetime timezone error in refresh-all endpoint - Added _to_naive_utc() helper for PostgreSQL compatibility Frontend: - Watchlist now passes domain status to AnalyzePanel - Status is consistent between Drops, Watchlist, and Sidepanel - Shows "Available" or "Taken" status in AnalyzePanel from Watchlist
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
"""Domain management API (requires authentication)."""
|
"""Domain management API (requires authentication)."""
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, status, Query
|
from fastapi import APIRouter, HTTPException, status, Query
|
||||||
@ -16,6 +16,16 @@ from app.services.domain_health import get_health_checker, HealthStatus
|
|||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
def _to_naive_utc(dt: datetime | None) -> datetime | None:
|
||||||
|
"""Convert timezone-aware datetime to naive UTC datetime for PostgreSQL."""
|
||||||
|
if dt is None:
|
||||||
|
return None
|
||||||
|
if dt.tzinfo is not None:
|
||||||
|
# Convert to UTC and remove timezone info
|
||||||
|
return dt.astimezone(timezone.utc).replace(tzinfo=None)
|
||||||
|
return dt
|
||||||
|
|
||||||
def _safe_json_loads(value: str | None, default):
|
def _safe_json_loads(value: str | None, default):
|
||||||
if not value:
|
if not value:
|
||||||
return default
|
return default
|
||||||
@ -265,7 +275,7 @@ async def refresh_domain(
|
|||||||
domain.status = check_result.status
|
domain.status = check_result.status
|
||||||
domain.is_available = check_result.is_available
|
domain.is_available = check_result.is_available
|
||||||
domain.registrar = check_result.registrar
|
domain.registrar = check_result.registrar
|
||||||
domain.expiration_date = check_result.expiration_date
|
domain.expiration_date = _to_naive_utc(check_result.expiration_date)
|
||||||
domain.last_checked = datetime.utcnow()
|
domain.last_checked = datetime.utcnow()
|
||||||
|
|
||||||
# Create check record
|
# Create check record
|
||||||
@ -342,7 +352,7 @@ async def refresh_all_domains(
|
|||||||
domain.status = check_result.status
|
domain.status = check_result.status
|
||||||
domain.is_available = check_result.is_available
|
domain.is_available = check_result.is_available
|
||||||
domain.registrar = check_result.registrar
|
domain.registrar = check_result.registrar
|
||||||
domain.expiration_date = check_result.expiration_date
|
domain.expiration_date = _to_naive_utc(check_result.expiration_date)
|
||||||
domain.last_checked = datetime.utcnow()
|
domain.last_checked = datetime.utcnow()
|
||||||
|
|
||||||
# Create check record
|
# Create check record
|
||||||
|
|||||||
@ -147,7 +147,16 @@ const healthConfig: Record<HealthStatus, { label: string; color: string; bg: str
|
|||||||
export default function WatchlistPage() {
|
export default function WatchlistPage() {
|
||||||
const { domains, addDomain, deleteDomain, refreshDomain, updateDomain, subscription, user, logout, checkAuth } = useStore()
|
const { domains, addDomain, deleteDomain, refreshDomain, updateDomain, subscription, user, logout, checkAuth } = useStore()
|
||||||
const { toast, showToast, hideToast } = useToast()
|
const { toast, showToast, hideToast } = useToast()
|
||||||
const openAnalyze = useAnalyzePanelStore((s) => s.open)
|
const openAnalyzePanel = useAnalyzePanelStore((s) => s.open)
|
||||||
|
|
||||||
|
// Wrapper to open analyze panel with domain status
|
||||||
|
const openAnalyze = useCallback((domainData: { name: string; is_available: boolean; expiration_date: string | null }) => {
|
||||||
|
openAnalyzePanel(domainData.name, {
|
||||||
|
status: domainData.is_available ? 'available' : 'taken',
|
||||||
|
deletion_date: domainData.expiration_date,
|
||||||
|
is_drop: false,
|
||||||
|
})
|
||||||
|
}, [openAnalyzePanel])
|
||||||
|
|
||||||
// Modal state
|
// Modal state
|
||||||
const [showAddModal, setShowAddModal] = useState(false)
|
const [showAddModal, setShowAddModal] = useState(false)
|
||||||
@ -608,7 +617,7 @@ export default function WatchlistPage() {
|
|||||||
<div className="flex items-start justify-between gap-4 mb-4">
|
<div className="flex items-start justify-between gap-4 mb-4">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => openAnalyze(domain.name)}
|
onClick={() => openAnalyze(domain)}
|
||||||
className="text-lg font-bold text-white font-mono truncate block text-left hover:text-accent transition-colors"
|
className="text-lg font-bold text-white font-mono truncate block text-left hover:text-accent transition-colors"
|
||||||
>
|
>
|
||||||
{domain.name}
|
{domain.name}
|
||||||
@ -667,7 +676,7 @@ export default function WatchlistPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => openAnalyze(domain.name)}
|
onClick={() => openAnalyze(domain)}
|
||||||
className="w-14 h-12 border border-white/10 text-white/50 flex items-center justify-center hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
className="w-14 h-12 border border-white/10 text-white/50 flex items-center justify-center hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
||||||
>
|
>
|
||||||
<Shield className="w-5 h-5" />
|
<Shield className="w-5 h-5" />
|
||||||
@ -695,7 +704,7 @@ export default function WatchlistPage() {
|
|||||||
{/* Domain */}
|
{/* Domain */}
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => openAnalyze(domain.name)}
|
onClick={() => openAnalyze(domain)}
|
||||||
className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left"
|
className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left"
|
||||||
title="Analyze"
|
title="Analyze"
|
||||||
>
|
>
|
||||||
@ -792,7 +801,7 @@ export default function WatchlistPage() {
|
|||||||
<RefreshCw className={clsx("w-4 h-4", refreshingId === domain.id && "animate-spin")} />
|
<RefreshCw className={clsx("w-4 h-4", refreshingId === domain.id && "animate-spin")} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => openAnalyze(domain.name)}
|
onClick={() => openAnalyze(domain)}
|
||||||
title="Analyze"
|
title="Analyze"
|
||||||
className="w-10 h-10 flex items-center justify-center text-white/40 hover:text-accent border border-white/10 hover:bg-accent/10 hover:border-accent/20 transition-all"
|
className="w-10 h-10 flex items-center justify-center text-white/40 hover:text-accent border border-white/10 hover:bg-accent/10 hover:border-accent/20 transition-all"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user