Deploy: 2025-12-19 12:10
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:
@ -89,6 +89,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
const [maxLength, setMaxLength] = useState<number | undefined>(undefined)
|
const [maxLength, setMaxLength] = useState<number | undefined>(undefined)
|
||||||
const [excludeNumeric, setExcludeNumeric] = useState(true)
|
const [excludeNumeric, setExcludeNumeric] = useState(true)
|
||||||
const [excludeHyphen, setExcludeHyphen] = useState(true)
|
const [excludeHyphen, setExcludeHyphen] = useState(true)
|
||||||
|
const [showOnlyAvailable, setShowOnlyAvailable] = useState(false)
|
||||||
const [filtersOpen, setFiltersOpen] = useState(false)
|
const [filtersOpen, setFiltersOpen] = useState(false)
|
||||||
|
|
||||||
// Pagination
|
// Pagination
|
||||||
@ -203,10 +204,17 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
}
|
}
|
||||||
}, [trackingDrop, showToast])
|
}, [trackingDrop, showToast])
|
||||||
|
|
||||||
// Sorted Items
|
// Filtered and Sorted Items
|
||||||
const sortedItems = useMemo(() => {
|
const sortedItems = useMemo(() => {
|
||||||
|
// Filter first if "show only available" is enabled
|
||||||
|
let filtered = items
|
||||||
|
if (showOnlyAvailable) {
|
||||||
|
filtered = items.filter(item => item.availability_status === 'available')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then sort
|
||||||
const mult = sortDirection === 'asc' ? 1 : -1
|
const mult = sortDirection === 'asc' ? 1 : -1
|
||||||
return [...items].sort((a, b) => {
|
return [...filtered].sort((a, b) => {
|
||||||
switch (sortField) {
|
switch (sortField) {
|
||||||
case 'domain':
|
case 'domain':
|
||||||
return mult * a.domain.localeCompare(b.domain)
|
return mult * a.domain.localeCompare(b.domain)
|
||||||
@ -218,7 +226,12 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [items, sortField, sortDirection])
|
}, [items, sortField, sortDirection, showOnlyAvailable])
|
||||||
|
|
||||||
|
// Count available domains
|
||||||
|
const availableCount = useMemo(() =>
|
||||||
|
items.filter(item => item.availability_status === 'available').length
|
||||||
|
, [items])
|
||||||
|
|
||||||
const handleSort = useCallback((field: typeof sortField) => {
|
const handleSort = useCallback((field: typeof sortField) => {
|
||||||
if (sortField === field) {
|
if (sortField === field) {
|
||||||
@ -236,6 +249,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
maxLength !== undefined,
|
maxLength !== undefined,
|
||||||
excludeNumeric,
|
excludeNumeric,
|
||||||
excludeHyphen,
|
excludeHyphen,
|
||||||
|
showOnlyAvailable,
|
||||||
].filter(Boolean).length
|
].filter(Boolean).length
|
||||||
|
|
||||||
const formatTime = (iso: string) => {
|
const formatTime = (iso: string) => {
|
||||||
@ -426,17 +440,47 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
{excludeHyphen && <CheckCircle2 className="w-3 h-3 text-black" />}
|
{excludeHyphen && <CheckCircle2 className="w-3 h-3 text-black" />}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* Show Only Available Filter */}
|
||||||
|
<button
|
||||||
|
onClick={() => setShowOnlyAvailable(!showOnlyAvailable)}
|
||||||
|
className={clsx(
|
||||||
|
"flex items-center justify-between py-3 px-4 border transition-all",
|
||||||
|
showOnlyAvailable ? "border-accent/30 bg-accent/5" : "border-white/[0.08] hover:border-white/20"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Zap className={clsx("w-4 h-4", showOnlyAvailable ? "text-accent" : "text-white/30")} />
|
||||||
|
<span className={clsx("text-xs font-mono uppercase tracking-wider", showOnlyAvailable ? "text-accent" : "text-white/50")}>
|
||||||
|
Only Available
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={clsx(
|
||||||
|
"w-5 h-5 border flex items-center justify-center transition-all",
|
||||||
|
showOnlyAvailable ? "border-accent bg-accent" : "border-white/20"
|
||||||
|
)}>
|
||||||
|
{showOnlyAvailable && <CheckCircle2 className="w-3 h-3 text-black" />}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Results Info */}
|
{/* Results Info */}
|
||||||
<div className="flex items-center justify-between px-1">
|
<div className="flex items-center justify-between px-1">
|
||||||
<div className="flex items-center gap-3 text-[11px] font-mono text-white/40 uppercase tracking-widest">
|
<div className="flex items-center gap-4 text-[11px] font-mono uppercase tracking-widest">
|
||||||
|
<div className="flex items-center gap-2 text-white/40">
|
||||||
<div className="w-1.5 h-1.5 bg-accent rounded-full animate-pulse" />
|
<div className="w-1.5 h-1.5 bg-accent rounded-full animate-pulse" />
|
||||||
<span>{total.toLocaleString()} domains detected</span>
|
<span>{showOnlyAvailable ? sortedItems.length : total.toLocaleString()} domains</span>
|
||||||
</div>
|
</div>
|
||||||
{totalPages > 1 && (
|
{availableCount > 0 && !showOnlyAvailable && (
|
||||||
|
<div className="flex items-center gap-2 text-accent">
|
||||||
|
<Zap className="w-3 h-3" />
|
||||||
|
<span>{availableCount} available now</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{totalPages > 1 && !showOnlyAvailable && (
|
||||||
<span className="text-[11px] font-mono text-white/30 uppercase tracking-widest">
|
<span className="text-[11px] font-mono text-white/30 uppercase tracking-widest">
|
||||||
Page {page} of {totalPages}
|
Page {page} of {totalPages}
|
||||||
</span>
|
</span>
|
||||||
@ -537,6 +581,17 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
{/* Track Button - always visible */}
|
||||||
|
<button
|
||||||
|
onClick={() => trackDrop(item.id, fullDomain)}
|
||||||
|
disabled={isTrackingThis}
|
||||||
|
className="h-12 px-4 border border-white/10 text-white/60 text-xs font-bold uppercase tracking-widest flex items-center justify-center gap-2 hover:bg-white/5 active:scale-[0.98] transition-all"
|
||||||
|
>
|
||||||
|
{isTrackingThis ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
|
||||||
|
Track
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Action Button based on status */}
|
||||||
{status === 'available' ? (
|
{status === 'available' ? (
|
||||||
<a
|
<a
|
||||||
href={`https://www.namecheap.com/domains/registration/results/?domain=${fullDomain}`}
|
href={`https://www.namecheap.com/domains/registration/results/?domain=${fullDomain}`}
|
||||||
@ -548,14 +603,10 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
Buy Now
|
Buy Now
|
||||||
</a>
|
</a>
|
||||||
) : status === 'dropping_soon' ? (
|
) : status === 'dropping_soon' ? (
|
||||||
<button
|
<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">
|
||||||
onClick={() => trackDrop(item.id, fullDomain)}
|
<Clock className="w-4 h-4" />
|
||||||
disabled={isTrackingThis}
|
Dropping Soon
|
||||||
className="flex-1 h-12 bg-amber-500 text-black text-xs font-black uppercase tracking-widest flex items-center justify-center gap-2 hover:bg-amber-400 active:scale-[0.98] transition-all"
|
</span>
|
||||||
>
|
|
||||||
{isTrackingThis ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
|
|
||||||
Track & Notify
|
|
||||||
</button>
|
|
||||||
) : 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" />
|
||||||
@ -627,8 +678,17 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions - simplified */}
|
{/* Actions */}
|
||||||
<div className="flex items-center justify-end gap-2 opacity-60 group-hover:opacity-100 transition-all">
|
<div className="flex items-center justify-end gap-2 opacity-60 group-hover:opacity-100 transition-all">
|
||||||
|
{/* Track Button - always visible */}
|
||||||
|
<button
|
||||||
|
onClick={() => trackDrop(item.id, fullDomain)}
|
||||||
|
disabled={isTrackingThis}
|
||||||
|
className="w-9 h-9 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 transition-all"
|
||||||
|
title="Add to Watchlist"
|
||||||
|
>
|
||||||
|
{isTrackingThis ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-3.5 h-3.5" />}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => openAnalyze(fullDomain)}
|
onClick={() => openAnalyze(fullDomain)}
|
||||||
className="w-9 h-9 flex items-center justify-center border border-white/10 text-white/50 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
className="w-9 h-9 flex items-center justify-center border border-white/10 text-white/50 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
||||||
@ -650,17 +710,12 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
Buy Now
|
Buy Now
|
||||||
</a>
|
</a>
|
||||||
) : status === 'dropping_soon' ? (
|
) : status === 'dropping_soon' ? (
|
||||||
<button
|
<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">
|
||||||
onClick={() => trackDrop(item.id, fullDomain)}
|
<Clock className="w-3 h-3" />
|
||||||
disabled={isTrackingThis}
|
Soon
|
||||||
className="h-9 px-4 bg-amber-500 text-black text-[10px] font-black uppercase tracking-widest flex items-center gap-1.5 hover:bg-amber-400 transition-all"
|
</span>
|
||||||
title="Add to Watchlist & get notified!"
|
|
||||||
>
|
|
||||||
{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-4 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" />
|
||||||
Taken
|
Taken
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user