feat: Consistent domain status across all pages
Backend: - Added DROPPING_SOON status to DomainStatus enum - Added deletion_date field to Domain model - domain_checker now returns DROPPING_SOON for pending delete - Track endpoint copies status and deletion_date from drop Frontend: - Watchlist shows "TRANSITION" status for dropping_soon domains - AnalyzePanel shows consistent status from Watchlist - Status display unified between Drops, Watchlist, and Panel
This commit is contained in:
@ -326,3 +326,5 @@ Empfehlungen:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -284,12 +284,22 @@ async def api_track_drop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Map drop status to Domain status
|
||||||
|
status_map = {
|
||||||
|
'available': DomainStatus.AVAILABLE,
|
||||||
|
'dropping_soon': DomainStatus.DROPPING_SOON,
|
||||||
|
'taken': DomainStatus.TAKEN,
|
||||||
|
'unknown': DomainStatus.UNKNOWN,
|
||||||
|
}
|
||||||
|
domain_status = status_map.get(drop.availability_status, DomainStatus.UNKNOWN)
|
||||||
|
|
||||||
# Add to watchlist with notification enabled
|
# Add to watchlist with notification enabled
|
||||||
domain = Domain(
|
domain = Domain(
|
||||||
user_id=current_user.id,
|
user_id=current_user.id,
|
||||||
name=full_domain,
|
name=full_domain,
|
||||||
status=DomainStatus.AVAILABLE if drop.availability_status == 'available' else DomainStatus.UNKNOWN,
|
status=domain_status,
|
||||||
is_available=drop.availability_status == 'available',
|
is_available=drop.availability_status == 'available',
|
||||||
|
deletion_date=drop.deletion_date, # Copy deletion date for countdown
|
||||||
notify_on_available=True, # Enable notification!
|
notify_on_available=True, # Enable notification!
|
||||||
)
|
)
|
||||||
db.add(domain)
|
db.add(domain)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ class DomainStatus(str, Enum):
|
|||||||
"""Domain availability status."""
|
"""Domain availability status."""
|
||||||
AVAILABLE = "available"
|
AVAILABLE = "available"
|
||||||
TAKEN = "taken"
|
TAKEN = "taken"
|
||||||
|
DROPPING_SOON = "dropping_soon" # In transition/pending delete
|
||||||
ERROR = "error"
|
ERROR = "error"
|
||||||
UNKNOWN = "unknown"
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ class Domain(Base):
|
|||||||
# WHOIS data (optional)
|
# WHOIS data (optional)
|
||||||
registrar: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
registrar: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||||
expiration_date: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
expiration_date: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||||
|
deletion_date: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) # When domain will be fully deleted
|
||||||
|
|
||||||
# User relationship
|
# User relationship
|
||||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
|
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
|
||||||
|
|||||||
@ -197,11 +197,11 @@ class DomainChecker:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if is_pending_delete:
|
if is_pending_delete:
|
||||||
logger.info(f"{domain} is pending delete (status: {domain_status})")
|
logger.info(f"{domain} is in transition/pending delete (status: {domain_status})")
|
||||||
return DomainCheckResult(
|
return DomainCheckResult(
|
||||||
domain=domain,
|
domain=domain,
|
||||||
status=DomainStatus.AVAILABLE,
|
status=DomainStatus.DROPPING_SOON, # In transition, not yet available
|
||||||
is_available=True,
|
is_available=False, # Not yet registrable
|
||||||
check_method="rdap_custom",
|
check_method="rdap_custom",
|
||||||
raw_data={"rdap_status": domain_status, "note": "pending_delete"},
|
raw_data={"rdap_status": domain_status, "note": "pending_delete"},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -150,10 +150,18 @@ export default function WatchlistPage() {
|
|||||||
const openAnalyzePanel = useAnalyzePanelStore((s) => s.open)
|
const openAnalyzePanel = useAnalyzePanelStore((s) => s.open)
|
||||||
|
|
||||||
// Wrapper to open analyze panel with domain status
|
// Wrapper to open analyze panel with domain status
|
||||||
const openAnalyze = useCallback((domainData: { name: string; is_available: boolean; expiration_date: string | null }) => {
|
const openAnalyze = useCallback((domainData: { name: string; status: string; is_available: boolean; expiration_date: string | null; deletion_date?: string | null }) => {
|
||||||
|
// Map domain status to drop status format
|
||||||
|
const statusMap: Record<string, 'available' | 'dropping_soon' | 'taken' | 'unknown'> = {
|
||||||
|
'available': 'available',
|
||||||
|
'dropping_soon': 'dropping_soon',
|
||||||
|
'taken': 'taken',
|
||||||
|
'error': 'unknown',
|
||||||
|
'unknown': 'unknown',
|
||||||
|
}
|
||||||
openAnalyzePanel(domainData.name, {
|
openAnalyzePanel(domainData.name, {
|
||||||
status: domainData.is_available ? 'available' : 'taken',
|
status: statusMap[domainData.status] || (domainData.is_available ? 'available' : 'taken'),
|
||||||
deletion_date: domainData.expiration_date,
|
deletion_date: domainData.deletion_date || domainData.expiration_date,
|
||||||
is_drop: false,
|
is_drop: false,
|
||||||
})
|
})
|
||||||
}, [openAnalyzePanel])
|
}, [openAnalyzePanel])
|
||||||
@ -607,6 +615,16 @@ export default function WatchlistPage() {
|
|||||||
const config = healthConfig[healthStatus]
|
const config = healthConfig[healthStatus]
|
||||||
const days = getDaysUntilExpiry(domain.expiration_date)
|
const days = getDaysUntilExpiry(domain.expiration_date)
|
||||||
|
|
||||||
|
// Domain status display config (consistent with DropsTab)
|
||||||
|
const domainStatus = domain.status || (domain.is_available ? 'available' : 'taken')
|
||||||
|
const statusConfig = {
|
||||||
|
available: { label: 'AVAIL', color: 'text-accent', bg: 'bg-accent/5 border-accent/20' },
|
||||||
|
dropping_soon: { label: 'TRANSITION', color: 'text-amber-400', bg: 'bg-amber-400/5 border-amber-400/20' },
|
||||||
|
taken: { label: 'TAKEN', color: 'text-white/40', bg: 'bg-white/5 border-white/10' },
|
||||||
|
error: { label: 'ERROR', color: 'text-rose-400', bg: 'bg-rose-400/5 border-rose-400/20' },
|
||||||
|
unknown: { label: 'CHECK', color: 'text-white/30', bg: 'bg-white/5 border-white/5' },
|
||||||
|
}[domainStatus] || { label: 'UNKNOWN', color: 'text-white/30', bg: 'bg-white/5 border-white/5' }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={domain.id}
|
key={domain.id}
|
||||||
@ -633,11 +651,9 @@ export default function WatchlistPage() {
|
|||||||
<div className="text-right shrink-0">
|
<div className="text-right shrink-0">
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
"text-[10px] font-mono px-2 py-0.5 mt-1 inline-block border",
|
"text-[10px] font-mono px-2 py-0.5 mt-1 inline-block border",
|
||||||
domain.is_available
|
statusConfig.color, statusConfig.bg
|
||||||
? "text-accent bg-accent/5 border-accent/20"
|
|
||||||
: "text-white/30 bg-white/5 border-white/5"
|
|
||||||
)}>
|
)}>
|
||||||
{domain.is_available ? 'AVAIL' : 'TAKEN'}
|
{statusConfig.label}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -719,11 +735,9 @@ export default function WatchlistPage() {
|
|||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<span className={clsx(
|
<span className={clsx(
|
||||||
"text-[10px] font-mono font-bold uppercase px-2.5 py-1.5 border",
|
"text-[10px] font-mono font-bold uppercase px-2.5 py-1.5 border",
|
||||||
domain.is_available
|
statusConfig.color, statusConfig.bg
|
||||||
? "text-accent bg-accent/10 border-accent/30"
|
|
||||||
: "text-white/40 bg-white/5 border-white/10"
|
|
||||||
)}>
|
)}>
|
||||||
{domain.is_available ? 'AVAIL' : 'TAKEN'}
|
{statusConfig.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user