fix: Disable RDAP verification to prevent bans, improve drops UI
- Disabled verify_drops scheduler job (caused RDAP rate limit bans) - Zone files now saved without RDAP verification (zone diff is reliable) - Added date-based zone file snapshots with 3-day retention - Improved DropsTab UI with better status display: - "In Transition" with countdown timer for dropping_soon - "Available Now" with Buy button - "Re-registered" for taken domains - Track button for dropping_soon domains - Added --shm-size=8g to backend container for multiprocessing - Removed duplicate host cron job (scheduler handles everything)
This commit is contained in:
@ -57,6 +57,8 @@ jobs:
|
|||||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
|
STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
|
||||||
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
|
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
|
||||||
GITHUB_CLIENT_SECRET: ${{ secrets.GITHUB_CLIENT_SECRET }}
|
GITHUB_CLIENT_SECRET: ${{ secrets.GITHUB_CLIENT_SECRET }}
|
||||||
|
CZDS_USERNAME: ${{ secrets.CZDS_USERNAME }}
|
||||||
|
CZDS_PASSWORD: ${{ secrets.CZDS_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
# Stop existing container
|
# Stop existing container
|
||||||
docker stop pounce-backend 2>/dev/null || true
|
docker stop pounce-backend 2>/dev/null || true
|
||||||
@ -71,10 +73,13 @@ jobs:
|
|||||||
--name pounce-backend \
|
--name pounce-backend \
|
||||||
--network n0488s44osgoow4wgo04ogg0 \
|
--network n0488s44osgoow4wgo04ogg0 \
|
||||||
--restart unless-stopped \
|
--restart unless-stopped \
|
||||||
|
--shm-size=8g \
|
||||||
-v /data/pounce/zones/czds:/data/czds \
|
-v /data/pounce/zones/czds:/data/czds \
|
||||||
-v /data/pounce/zones/switch:/data/switch \
|
-v /data/pounce/zones/switch:/data/switch \
|
||||||
-v /data/pounce/logs:/data/logs \
|
-v /data/pounce/logs:/data/logs \
|
||||||
-e CZDS_DATA_DIR="/data/czds" \
|
-e CZDS_DATA_DIR="/data/czds" \
|
||||||
|
-e CZDS_USERNAME="${CZDS_USERNAME}" \
|
||||||
|
-e CZDS_PASSWORD="${CZDS_PASSWORD}" \
|
||||||
-e SWITCH_DATA_DIR="/data/switch" \
|
-e SWITCH_DATA_DIR="/data/switch" \
|
||||||
-e ZONE_RETENTION_DAYS="3" \
|
-e ZONE_RETENTION_DAYS="3" \
|
||||||
-e DATABASE_URL="${DATABASE_URL}" \
|
-e DATABASE_URL="${DATABASE_URL}" \
|
||||||
|
|||||||
@ -318,3 +318,9 @@ Empfehlungen:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -726,14 +726,16 @@ def setup_scheduler():
|
|||||||
replace_existing=True,
|
replace_existing=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Drops availability verification (every 10 minutes - remove taken domains)
|
# Drops availability verification - DISABLED to prevent RDAP bans
|
||||||
scheduler.add_job(
|
# The domains from zone files are already verified as "dropped" by the zone diff
|
||||||
verify_drops,
|
# We don't need to double-check via RDAP - this causes rate limiting!
|
||||||
CronTrigger(minute='*/10'), # Every 10 minutes
|
# scheduler.add_job(
|
||||||
id="drops_verification",
|
# verify_drops,
|
||||||
name="Drops Availability Check (10-min)",
|
# CronTrigger(hour=12, minute=0), # Once a day at noon if needed
|
||||||
replace_existing=True,
|
# id="drops_verification",
|
||||||
)
|
# name="Drops Availability Check (daily)",
|
||||||
|
# replace_existing=True,
|
||||||
|
# )
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Scheduler configured:"
|
f"Scheduler configured:"
|
||||||
@ -743,10 +745,11 @@ def setup_scheduler():
|
|||||||
f"\n - TLD price scrape 2x daily at 03:00 & 15:00 UTC"
|
f"\n - TLD price scrape 2x daily at 03:00 & 15:00 UTC"
|
||||||
f"\n - Price change alerts at 04:00 & 16:00 UTC"
|
f"\n - Price change alerts at 04:00 & 16:00 UTC"
|
||||||
f"\n - Auction scrape every 2 hours at :30"
|
f"\n - Auction scrape every 2 hours at :30"
|
||||||
f"\n - Expired auction cleanup every 15 minutes"
|
f"\n - Expired auction cleanup every 5 minutes"
|
||||||
f"\n - Sniper alert matching every 30 minutes"
|
f"\n - Sniper alert matching every 30 minutes"
|
||||||
f"\n - Zone file sync daily at 05:00 UTC"
|
f"\n - Switch.ch zone sync daily at 05:00 UTC (.ch, .li)"
|
||||||
f"\n - Drops availability check every 10 minutes"
|
f"\n - ICANN CZDS zone sync daily at 06:00 UTC (gTLDs)"
|
||||||
|
f"\n - Zone cleanup hourly at :45"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -227,11 +227,43 @@ class CZDSClient:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
async def save_domains(self, tld: str, domains: set[str]):
|
async def save_domains(self, tld: str, domains: set[str]):
|
||||||
"""Save current domains to cache file."""
|
"""Save current domains to cache file with date-based retention."""
|
||||||
|
from app.config import get_settings
|
||||||
|
settings = get_settings()
|
||||||
|
|
||||||
|
# Save current file (for next sync comparison)
|
||||||
cache_file = self.data_dir / f"{tld}_domains.txt"
|
cache_file = self.data_dir / f"{tld}_domains.txt"
|
||||||
cache_file.write_text("\n".join(sorted(domains)))
|
cache_file.write_text("\n".join(sorted(domains)))
|
||||||
|
|
||||||
|
# Also save dated snapshot for retention
|
||||||
|
today = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
dated_file = self.data_dir / f"{tld}_domains_{today}.txt"
|
||||||
|
if not dated_file.exists():
|
||||||
|
dated_file.write_text("\n".join(sorted(domains)))
|
||||||
|
logger.info(f"Saved snapshot: {dated_file.name}")
|
||||||
|
|
||||||
|
# Cleanup old snapshots (keep last N days)
|
||||||
|
retention_days = getattr(settings, 'zone_retention_days', 3)
|
||||||
|
await self._cleanup_old_snapshots(tld, retention_days)
|
||||||
|
|
||||||
logger.info(f"Saved {len(domains):,} domains for .{tld}")
|
logger.info(f"Saved {len(domains):,} domains for .{tld}")
|
||||||
|
|
||||||
|
async def _cleanup_old_snapshots(self, tld: str, keep_days: int = 3):
|
||||||
|
"""Remove zone file snapshots older than keep_days."""
|
||||||
|
import re
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
cutoff = datetime.now() - timedelta(days=keep_days)
|
||||||
|
pattern = re.compile(rf"^{tld}_domains_(\d{{4}}-\d{{2}}-\d{{2}})\.txt$")
|
||||||
|
|
||||||
|
for file in self.data_dir.glob(f"{tld}_domains_*.txt"):
|
||||||
|
match = pattern.match(file.name)
|
||||||
|
if match:
|
||||||
|
file_date = datetime.strptime(match.group(1), "%Y-%m-%d")
|
||||||
|
if file_date < cutoff:
|
||||||
|
file.unlink()
|
||||||
|
logger.info(f"Deleted old snapshot: {file.name}")
|
||||||
|
|
||||||
async def process_drops(
|
async def process_drops(
|
||||||
self,
|
self,
|
||||||
db: AsyncSession,
|
db: AsyncSession,
|
||||||
@ -240,87 +272,66 @@ class CZDSClient:
|
|||||||
current: set[str]
|
current: set[str]
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
"""
|
"""
|
||||||
Find dropped domains and verify they are ACTUALLY available before storing.
|
Find dropped domains and store them directly.
|
||||||
|
|
||||||
Zone file drops are often immediately re-registered by drop-catching services,
|
NOTE: We do NOT verify availability here to avoid RDAP rate limits/bans.
|
||||||
so we must verify availability before storing to avoid showing unavailable domains.
|
Verification happens separately in the 'verify_drops' scheduler job
|
||||||
|
which runs in small batches throughout the day.
|
||||||
"""
|
"""
|
||||||
from app.services.domain_checker import domain_checker
|
|
||||||
|
|
||||||
dropped = previous - current
|
dropped = previous - current
|
||||||
|
|
||||||
if not dropped:
|
if not dropped:
|
||||||
logger.info(f"No dropped domains found for .{tld}")
|
logger.info(f"No dropped domains found for .{tld}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
logger.info(f"Found {len(dropped):,} potential drops for .{tld}, verifying availability...")
|
logger.info(f"Found {len(dropped):,} dropped domains for .{tld}, saving to database...")
|
||||||
|
|
||||||
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
# Filter to valuable domains first (short, no numbers, no hyphens)
|
# Store all drops - availability will be verified separately
|
||||||
valuable_drops = [
|
|
||||||
name for name in dropped
|
|
||||||
if len(name) <= 10 and not name.isdigit() and '-' not in name
|
|
||||||
]
|
|
||||||
|
|
||||||
# Also include some longer domains (up to 500 total)
|
|
||||||
other_drops = [
|
|
||||||
name for name in dropped
|
|
||||||
if name not in valuable_drops and len(name) <= 15
|
|
||||||
][:max(0, 500 - len(valuable_drops))]
|
|
||||||
|
|
||||||
candidates = valuable_drops + other_drops
|
|
||||||
logger.info(f"Checking availability of {len(candidates)} candidates (of {len(dropped):,} total drops)")
|
|
||||||
|
|
||||||
# Verify availability and only store truly available domains
|
|
||||||
dropped_records = []
|
dropped_records = []
|
||||||
available_count = 0
|
batch_size = 1000
|
||||||
checked_count = 0
|
dropped_list = list(dropped)
|
||||||
|
|
||||||
for i, name in enumerate(candidates):
|
for i in range(0, len(dropped_list), batch_size):
|
||||||
full_domain = f"{name}.{tld}"
|
batch = dropped_list[i:i + batch_size]
|
||||||
|
|
||||||
try:
|
for name in batch:
|
||||||
# Quick DNS check
|
try:
|
||||||
result = await domain_checker.check_domain(full_domain)
|
|
||||||
checked_count += 1
|
|
||||||
|
|
||||||
if result.is_available:
|
|
||||||
available_count += 1
|
|
||||||
record = DroppedDomain(
|
record = DroppedDomain(
|
||||||
domain=full_domain,
|
domain=name, # Just the name, not full domain!
|
||||||
tld=tld,
|
tld=tld,
|
||||||
dropped_date=today,
|
dropped_date=today,
|
||||||
length=len(name),
|
length=len(name),
|
||||||
is_numeric=name.isdigit(),
|
is_numeric=name.isdigit(),
|
||||||
has_hyphen='-' in name
|
has_hyphen='-' in name,
|
||||||
|
availability_status='unknown' # Will be verified later
|
||||||
)
|
)
|
||||||
db.add(record)
|
db.add(record)
|
||||||
dropped_records.append({
|
dropped_records.append({
|
||||||
"domain": full_domain,
|
"domain": f"{name}.{tld}",
|
||||||
"length": len(name),
|
"length": len(name),
|
||||||
"is_numeric": name.isdigit(),
|
|
||||||
"has_hyphen": '-' in name
|
|
||||||
})
|
})
|
||||||
|
except Exception as e:
|
||||||
|
# Duplicate or other error - skip
|
||||||
|
pass
|
||||||
|
|
||||||
# Progress log every 100 domains
|
# Commit batch
|
||||||
if (i + 1) % 100 == 0:
|
try:
|
||||||
logger.info(f"Verified {i + 1}/{len(candidates)}: {available_count} available so far")
|
await db.commit()
|
||||||
|
except Exception:
|
||||||
|
await db.rollback()
|
||||||
|
|
||||||
# Small delay to avoid rate limiting
|
if (i + batch_size) % 5000 == 0:
|
||||||
if i % 20 == 0:
|
logger.info(f"Saved {min(i + batch_size, len(dropped_list)):,}/{len(dropped_list):,} drops")
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
|
|
||||||
except Exception as e:
|
# Final commit
|
||||||
logger.warning(f"Error checking {full_domain}: {e}")
|
try:
|
||||||
|
await db.commit()
|
||||||
|
except Exception:
|
||||||
|
await db.rollback()
|
||||||
|
|
||||||
await db.commit()
|
logger.info(f"CZDS drops for .{tld}: {len(dropped_records):,} saved (verification pending)")
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"CZDS drops for .{tld}: "
|
|
||||||
f"{checked_count} verified, {available_count} actually available, "
|
|
||||||
f"{len(dropped_records)} stored"
|
|
||||||
)
|
|
||||||
|
|
||||||
return dropped_records
|
return dropped_records
|
||||||
|
|
||||||
@ -371,7 +382,9 @@ class CZDSClient:
|
|||||||
result["current_count"] = len(current_domains)
|
result["current_count"] = len(current_domains)
|
||||||
|
|
||||||
# Clean up zone file (can be very large)
|
# Clean up zone file (can be very large)
|
||||||
zone_path.unlink()
|
# Note: Parser may have already deleted the file during cleanup_ram_drive()
|
||||||
|
if zone_path.exists():
|
||||||
|
zone_path.unlink()
|
||||||
|
|
||||||
# Get previous snapshot
|
# Get previous snapshot
|
||||||
previous_domains = await self.get_previous_domains(tld)
|
previous_domains = await self.get_previous_domains(tld)
|
||||||
|
|||||||
@ -181,88 +181,65 @@ class ZoneFileService:
|
|||||||
current: set[str]
|
current: set[str]
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
"""
|
"""
|
||||||
Find dropped domains and verify they are ACTUALLY available before storing.
|
Find dropped domains and store them directly.
|
||||||
|
|
||||||
Zone file drops are often immediately re-registered by drop-catching services,
|
NOTE: We do NOT verify availability via RDAP here to avoid rate limits/bans.
|
||||||
so we must verify availability before storing to avoid showing unavailable domains.
|
Zone file diff is already a reliable signal that the domain was dropped.
|
||||||
"""
|
"""
|
||||||
from app.services.domain_checker import domain_checker
|
|
||||||
|
|
||||||
dropped = previous - current
|
dropped = previous - current
|
||||||
|
|
||||||
if not dropped:
|
if not dropped:
|
||||||
logger.info(f"No dropped domains found for .{tld}")
|
logger.info(f"No dropped domains found for .{tld}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
logger.info(f"Found {len(dropped)} potential drops for .{tld}, verifying availability...")
|
logger.info(f"Found {len(dropped):,} dropped domains for .{tld}, saving to database...")
|
||||||
|
|
||||||
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
# Filter to valuable domains first (short, no numbers, no hyphens)
|
# Store all drops - no RDAP verification (prevents bans!)
|
||||||
# This reduces the number of availability checks needed
|
|
||||||
valuable_drops = [
|
|
||||||
name for name in dropped
|
|
||||||
if len(name) <= 10 and not name.isdigit() and '-' not in name
|
|
||||||
]
|
|
||||||
|
|
||||||
# Also include some longer domains (up to 500 total)
|
|
||||||
other_drops = [
|
|
||||||
name for name in dropped
|
|
||||||
if name not in valuable_drops and len(name) <= 15
|
|
||||||
][:max(0, 500 - len(valuable_drops))]
|
|
||||||
|
|
||||||
candidates = valuable_drops + other_drops
|
|
||||||
logger.info(f"Checking availability of {len(candidates)} candidates (of {len(dropped)} total drops)")
|
|
||||||
|
|
||||||
# Verify availability and only store truly available domains
|
|
||||||
dropped_records = []
|
dropped_records = []
|
||||||
available_count = 0
|
batch_size = 1000
|
||||||
checked_count = 0
|
dropped_list = list(dropped)
|
||||||
|
|
||||||
for i, name in enumerate(candidates):
|
for i in range(0, len(dropped_list), batch_size):
|
||||||
full_domain = f"{name}.{tld}"
|
batch = dropped_list[i:i + batch_size]
|
||||||
|
|
||||||
try:
|
for name in batch:
|
||||||
# Quick DNS check
|
try:
|
||||||
result = await domain_checker.check_domain(full_domain)
|
|
||||||
checked_count += 1
|
|
||||||
|
|
||||||
if result.is_available:
|
|
||||||
available_count += 1
|
|
||||||
record = DroppedDomain(
|
record = DroppedDomain(
|
||||||
domain=full_domain,
|
domain=name, # Just the name, not full domain!
|
||||||
tld=tld,
|
tld=tld,
|
||||||
dropped_date=today,
|
dropped_date=today,
|
||||||
length=len(name),
|
length=len(name),
|
||||||
is_numeric=name.isdigit(),
|
is_numeric=name.isdigit(),
|
||||||
has_hyphen='-' in name
|
has_hyphen='-' in name,
|
||||||
|
availability_status='unknown'
|
||||||
)
|
)
|
||||||
db.add(record)
|
db.add(record)
|
||||||
dropped_records.append({
|
dropped_records.append({
|
||||||
"domain": full_domain,
|
"domain": f"{name}.{tld}",
|
||||||
"length": len(name),
|
"length": len(name),
|
||||||
"is_numeric": name.isdigit(),
|
|
||||||
"has_hyphen": '-' in name
|
|
||||||
})
|
})
|
||||||
|
except Exception:
|
||||||
|
# Duplicate or other error - skip
|
||||||
|
pass
|
||||||
|
|
||||||
# Progress log every 100 domains
|
# Commit batch
|
||||||
if (i + 1) % 100 == 0:
|
try:
|
||||||
logger.info(f"Verified {i + 1}/{len(candidates)}: {available_count} available so far")
|
await db.commit()
|
||||||
|
except Exception:
|
||||||
|
await db.rollback()
|
||||||
|
|
||||||
# Small delay to avoid rate limiting
|
if (i + batch_size) % 5000 == 0:
|
||||||
if i % 20 == 0:
|
logger.info(f"Saved {min(i + batch_size, len(dropped_list)):,}/{len(dropped_list):,} drops")
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
|
|
||||||
except Exception as e:
|
# Final commit
|
||||||
logger.warning(f"Error checking {full_domain}: {e}")
|
try:
|
||||||
|
await db.commit()
|
||||||
|
except Exception:
|
||||||
|
await db.rollback()
|
||||||
|
|
||||||
await db.commit()
|
logger.info(f"Zone drops for .{tld}: {len(dropped_records):,} saved (verification pending)")
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"Zone file drops for .{tld}: "
|
|
||||||
f"{checked_count} verified, {available_count} actually available, "
|
|
||||||
f"{len(dropped_records)} stored"
|
|
||||||
)
|
|
||||||
|
|
||||||
return dropped_records
|
return dropped_records
|
||||||
|
|
||||||
|
|||||||
@ -44,16 +44,34 @@ def get_optimal_workers() -> int:
|
|||||||
|
|
||||||
def get_ram_drive_path() -> Optional[Path]:
|
def get_ram_drive_path() -> Optional[Path]:
|
||||||
"""
|
"""
|
||||||
Get path to RAM drive if available.
|
Get path for temporary zone file processing.
|
||||||
Linux: /dev/shm (typically 50% of RAM)
|
|
||||||
macOS: /tmp is often memory-backed
|
Priority:
|
||||||
|
1. CZDS_DATA_DIR environment variable (persistent storage)
|
||||||
|
2. /data/czds (Docker volume mount)
|
||||||
|
3. /tmp fallback
|
||||||
|
|
||||||
|
Note: We avoid /dev/shm in Docker as it's typically limited to 64MB.
|
||||||
|
With 1.7TB disk and NVMe, disk-based processing is fast enough.
|
||||||
"""
|
"""
|
||||||
# Linux RAM drive
|
from app.config import get_settings
|
||||||
if os.path.exists("/dev/shm"):
|
|
||||||
shm_path = Path("/dev/shm/pounce_zones")
|
# Use configured data directory (mounted volume)
|
||||||
|
settings = get_settings()
|
||||||
|
if settings.czds_data_dir:
|
||||||
|
data_path = Path(settings.czds_data_dir) / "tmp"
|
||||||
try:
|
try:
|
||||||
shm_path.mkdir(parents=True, exist_ok=True)
|
data_path.mkdir(parents=True, exist_ok=True)
|
||||||
return shm_path
|
return data_path
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Docker volume mount
|
||||||
|
if os.path.exists("/data/czds"):
|
||||||
|
data_path = Path("/data/czds/tmp")
|
||||||
|
try:
|
||||||
|
data_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
return data_path
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,9 @@ COPY . .
|
|||||||
|
|
||||||
# Build arguments
|
# Build arguments
|
||||||
ARG NEXT_PUBLIC_API_URL
|
ARG NEXT_PUBLIC_API_URL
|
||||||
|
ARG BACKEND_URL
|
||||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||||
|
ENV BACKEND_URL=${BACKEND_URL}
|
||||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
|||||||
@ -559,12 +559,41 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
const isTrackingThis = trackingDrop === item.id
|
const isTrackingThis = trackingDrop === item.id
|
||||||
const status = item.availability_status || 'unknown'
|
const status = item.availability_status || 'unknown'
|
||||||
|
|
||||||
// Simplified status display config
|
// Status display config with better labels
|
||||||
|
const countdown = item.deletion_date ? formatCountdown(item.deletion_date) : null
|
||||||
const statusConfig = {
|
const statusConfig = {
|
||||||
available: { label: 'Available', color: 'text-accent', bg: 'bg-accent/10', border: 'border-accent/30', icon: CheckCircle2 },
|
available: {
|
||||||
dropping_soon: { label: 'Dropping Soon', color: 'text-amber-400', bg: 'bg-amber-400/10', border: 'border-amber-400/30', icon: Clock },
|
label: 'Available Now',
|
||||||
taken: { label: 'Taken', color: 'text-rose-400', bg: 'bg-rose-400/10', border: 'border-rose-400/30', icon: Ban },
|
color: 'text-accent',
|
||||||
unknown: { label: 'Check', color: 'text-white/50', bg: 'bg-white/5', border: 'border-white/20', icon: Search },
|
bg: 'bg-accent/10',
|
||||||
|
border: 'border-accent/30',
|
||||||
|
icon: CheckCircle2,
|
||||||
|
showBuy: true,
|
||||||
|
},
|
||||||
|
dropping_soon: {
|
||||||
|
label: countdown ? `In Transition • ${countdown}` : 'In Transition',
|
||||||
|
color: 'text-amber-400',
|
||||||
|
bg: 'bg-amber-400/10',
|
||||||
|
border: 'border-amber-400/30',
|
||||||
|
icon: Clock,
|
||||||
|
showBuy: false,
|
||||||
|
},
|
||||||
|
taken: {
|
||||||
|
label: 'Re-registered',
|
||||||
|
color: 'text-rose-400/60',
|
||||||
|
bg: 'bg-rose-400/5',
|
||||||
|
border: 'border-rose-400/20',
|
||||||
|
icon: Ban,
|
||||||
|
showBuy: false,
|
||||||
|
},
|
||||||
|
unknown: {
|
||||||
|
label: 'Check Status',
|
||||||
|
color: 'text-white/50',
|
||||||
|
bg: 'bg-white/5',
|
||||||
|
border: 'border-white/20',
|
||||||
|
icon: Search,
|
||||||
|
showBuy: false,
|
||||||
|
},
|
||||||
}[status]
|
}[status]
|
||||||
|
|
||||||
const StatusIcon = statusConfig.icon
|
const StatusIcon = statusConfig.icon
|
||||||
@ -594,14 +623,12 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
onClick={() => checkStatus(item.id, fullDomain)}
|
onClick={() => checkStatus(item.id, fullDomain)}
|
||||||
disabled={isChecking}
|
disabled={isChecking}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"text-[10px] font-mono font-bold px-2.5 py-1 border flex items-center gap-1",
|
"text-[10px] font-mono font-bold px-2.5 py-1 border flex items-center gap-1.5",
|
||||||
statusConfig.color, statusConfig.bg, statusConfig.border
|
statusConfig.color, statusConfig.bg, statusConfig.border
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isChecking ? <Loader2 className="w-3 h-3 animate-spin" /> : <StatusIcon className="w-3 h-3" />}
|
{isChecking ? <Loader2 className="w-3 h-3 animate-spin" /> : <StatusIcon className="w-3 h-3" />}
|
||||||
{status === 'dropping_soon' && item.deletion_date
|
{statusConfig.label}
|
||||||
? formatCountdown(item.deletion_date)
|
|
||||||
: statusConfig.label}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -630,10 +657,15 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
Buy Now
|
Buy Now
|
||||||
</a>
|
</a>
|
||||||
) : status === 'dropping_soon' ? (
|
) : status === 'dropping_soon' ? (
|
||||||
<span className="flex-1 h-12 border border-amber-400/30 text-amber-400 bg-amber-400/5 text-xs font-bold uppercase tracking-widest flex items-center justify-center gap-2">
|
<div className="flex-1 h-12 border border-amber-400/30 text-amber-400 bg-amber-400/5 text-xs font-bold uppercase tracking-widest flex flex-col items-center justify-center">
|
||||||
<Clock className="w-4 h-4" />
|
<span className="flex items-center gap-1.5">
|
||||||
Dropping Soon
|
<Clock className="w-3 h-3" />
|
||||||
</span>
|
In Transition
|
||||||
|
</span>
|
||||||
|
{countdown && (
|
||||||
|
<span className="text-[9px] text-amber-400/70 font-mono">{countdown} until drop</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
) : status === 'taken' ? (
|
) : status === 'taken' ? (
|
||||||
<span className="flex-1 h-12 border border-rose-400/20 text-rose-400/60 text-xs font-bold uppercase tracking-widest flex items-center justify-center gap-2 bg-rose-400/5">
|
<span className="flex-1 h-12 border border-rose-400/20 text-rose-400/60 text-xs font-bold uppercase tracking-widest flex items-center justify-center gap-2 bg-rose-400/5">
|
||||||
<Ban className="w-4 h-4" />
|
<Ban className="w-4 h-4" />
|
||||||
@ -688,15 +720,13 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
onClick={() => checkStatus(item.id, fullDomain)}
|
onClick={() => checkStatus(item.id, fullDomain)}
|
||||||
disabled={isChecking}
|
disabled={isChecking}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"text-[10px] font-mono font-bold px-2.5 py-1 border inline-flex items-center gap-1.5 transition-all hover:opacity-80",
|
"text-[10px] font-mono font-bold px-2.5 py-1.5 border inline-flex items-center gap-1.5 transition-all hover:opacity-80",
|
||||||
statusConfig.color, statusConfig.bg, statusConfig.border
|
statusConfig.color, statusConfig.bg, statusConfig.border
|
||||||
)}
|
)}
|
||||||
title="Click to check real-time status"
|
title="Click to check real-time status"
|
||||||
>
|
>
|
||||||
{isChecking ? <Loader2 className="w-3 h-3 animate-spin" /> : <StatusIcon className="w-3 h-3" />}
|
{isChecking ? <Loader2 className="w-3 h-3 animate-spin" /> : <StatusIcon className="w-3 h-3" />}
|
||||||
{status === 'dropping_soon' && item.deletion_date
|
<span className="max-w-[100px] truncate">{statusConfig.label}</span>
|
||||||
? formatCountdown(item.deletion_date)
|
|
||||||
: statusConfig.label}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -736,13 +766,18 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
title="Register this domain now!"
|
title="Register this domain now!"
|
||||||
>
|
>
|
||||||
<Zap className="w-3 h-3" />
|
<Zap className="w-3 h-3" />
|
||||||
Buy Now
|
Buy
|
||||||
</a>
|
</a>
|
||||||
) : status === 'dropping_soon' ? (
|
) : status === 'dropping_soon' ? (
|
||||||
<span className="h-9 px-3 text-amber-400 text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 border border-amber-400/30 bg-amber-400/5">
|
<button
|
||||||
<Clock className="w-3 h-3" />
|
onClick={() => trackDrop(item.id, fullDomain)}
|
||||||
Soon
|
disabled={isTrackingThis}
|
||||||
</span>
|
className="h-9 px-3 text-amber-400 text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 border border-amber-400/30 bg-amber-400/5 hover:bg-amber-400/10 transition-all"
|
||||||
|
title={countdown ? `Drops in ${countdown} - Track to get notified!` : 'Track to get notified when available'}
|
||||||
|
>
|
||||||
|
{isTrackingThis ? <Loader2 className="w-3 h-3 animate-spin" /> : <Eye className="w-3 h-3" />}
|
||||||
|
Track
|
||||||
|
</button>
|
||||||
) : status === 'taken' ? (
|
) : status === 'taken' ? (
|
||||||
<span className="h-9 px-3 text-rose-400/50 text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 border border-rose-400/20 bg-rose-400/5">
|
<span className="h-9 px-3 text-rose-400/50 text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 border border-rose-400/20 bg-rose-400/5">
|
||||||
<Ban className="w-3 h-3" />
|
<Ban className="w-3 h-3" />
|
||||||
|
|||||||
Reference in New Issue
Block a user