fix: Track endpoint error handling, improve drops UI with tracked state
Backend: - Fixed track endpoint duplicate key error with proper rollback - Returns domain_id for already tracked domains Frontend DropsTab: - Added trackedDrops state to show "Tracked" status - Track button shows checkmark when already in watchlist - Status button shows "In Transition" with countdown AnalyzePanel: - Added dropStatus to store for passing drop info - Shows Drop Status banner with availability - "Buy Now" button for available domains in panel
This commit is contained in:
@ -324,3 +324,5 @@ Empfehlungen:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -274,22 +274,49 @@ async def api_track_drop(
|
|||||||
Domain.name == full_domain
|
Domain.name == full_domain
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if existing.scalar_one_or_none():
|
existing_domain = existing.scalar_one_or_none()
|
||||||
return {"status": "already_tracking", "domain": full_domain}
|
if existing_domain:
|
||||||
|
return {
|
||||||
|
"status": "already_tracking",
|
||||||
|
"domain": full_domain,
|
||||||
|
"message": f"{full_domain} is already in your Watchlist",
|
||||||
|
"domain_id": existing_domain.id
|
||||||
|
}
|
||||||
|
|
||||||
# Add to watchlist with notification enabled
|
try:
|
||||||
domain = Domain(
|
# Add to watchlist with notification enabled
|
||||||
user_id=current_user.id,
|
domain = Domain(
|
||||||
name=full_domain,
|
user_id=current_user.id,
|
||||||
status=DomainStatus.AVAILABLE if drop.availability_status == 'available' else DomainStatus.UNKNOWN,
|
name=full_domain,
|
||||||
is_available=drop.availability_status == 'available',
|
status=DomainStatus.AVAILABLE if drop.availability_status == 'available' else DomainStatus.UNKNOWN,
|
||||||
notify_on_available=True, # Enable notification!
|
is_available=drop.availability_status == 'available',
|
||||||
)
|
notify_on_available=True, # Enable notification!
|
||||||
db.add(domain)
|
)
|
||||||
await db.commit()
|
db.add(domain)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(domain)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "tracking",
|
"status": "tracking",
|
||||||
"domain": full_domain,
|
"domain": full_domain,
|
||||||
"message": f"Added {full_domain} to your Watchlist. You'll be notified when available!"
|
"message": f"Added {full_domain} to your Watchlist. You'll be notified when available!",
|
||||||
}
|
"domain_id": domain.id
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
await db.rollback()
|
||||||
|
# If duplicate key error, try to find existing
|
||||||
|
existing = await db.execute(
|
||||||
|
select(Domain).where(
|
||||||
|
Domain.user_id == current_user.id,
|
||||||
|
Domain.name == full_domain
|
||||||
|
)
|
||||||
|
)
|
||||||
|
existing_domain = existing.scalar_one_or_none()
|
||||||
|
if existing_domain:
|
||||||
|
return {
|
||||||
|
"status": "already_tracking",
|
||||||
|
"domain": full_domain,
|
||||||
|
"message": f"{full_domain} is already in your Watchlist",
|
||||||
|
"domain_id": existing_domain.id
|
||||||
|
}
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|||||||
@ -178,7 +178,8 @@ export function AnalyzePanel() {
|
|||||||
fastMode,
|
fastMode,
|
||||||
setFastMode,
|
setFastMode,
|
||||||
sectionVisibility,
|
sectionVisibility,
|
||||||
setSectionVisibility
|
setSectionVisibility,
|
||||||
|
dropStatus,
|
||||||
} = useAnalyzePanelStore()
|
} = useAnalyzePanelStore()
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
@ -374,6 +375,61 @@ export function AnalyzePanel() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Drop Status Banner */}
|
||||||
|
{dropStatus?.is_drop && (
|
||||||
|
<div className="px-5 pb-3">
|
||||||
|
<div className={clsx(
|
||||||
|
"p-4 border flex items-center justify-between gap-4",
|
||||||
|
dropStatus.status === 'available' ? "border-accent/30 bg-accent/5" :
|
||||||
|
dropStatus.status === 'dropping_soon' ? "border-amber-400/30 bg-amber-400/5" :
|
||||||
|
dropStatus.status === 'taken' ? "border-rose-400/20 bg-rose-400/5" :
|
||||||
|
"border-white/10 bg-white/[0.02]"
|
||||||
|
)}>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{dropStatus.status === 'available' ? (
|
||||||
|
<CheckCircle2 className="w-5 h-5 text-accent" />
|
||||||
|
) : dropStatus.status === 'dropping_soon' ? (
|
||||||
|
<Clock className="w-5 h-5 text-amber-400" />
|
||||||
|
) : dropStatus.status === 'taken' ? (
|
||||||
|
<XCircle className="w-5 h-5 text-rose-400" />
|
||||||
|
) : (
|
||||||
|
<Globe className="w-5 h-5 text-white/40" />
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<div className={clsx(
|
||||||
|
"text-sm font-bold uppercase tracking-wider",
|
||||||
|
dropStatus.status === 'available' ? "text-accent" :
|
||||||
|
dropStatus.status === 'dropping_soon' ? "text-amber-400" :
|
||||||
|
dropStatus.status === 'taken' ? "text-rose-400" :
|
||||||
|
"text-white/50"
|
||||||
|
)}>
|
||||||
|
{dropStatus.status === 'available' ? 'Available Now' :
|
||||||
|
dropStatus.status === 'dropping_soon' ? 'In Transition' :
|
||||||
|
dropStatus.status === 'taken' ? 'Re-registered' :
|
||||||
|
'Status Unknown'}
|
||||||
|
</div>
|
||||||
|
{dropStatus.status === 'dropping_soon' && dropStatus.deletion_date && (
|
||||||
|
<div className="text-xs font-mono text-amber-400/70">
|
||||||
|
Drops: {new Date(dropStatus.deletion_date).toLocaleDateString()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{dropStatus.status === 'available' && domain && (
|
||||||
|
<a
|
||||||
|
href={`https://www.namecheap.com/domains/registration/results/?domain=${domain}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="h-9 px-4 bg-accent text-black text-[10px] font-black uppercase tracking-widest flex items-center gap-1.5 hover:bg-white transition-all"
|
||||||
|
>
|
||||||
|
<Zap className="w-3 h-3" />
|
||||||
|
Buy Now
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Controls */}
|
{/* Controls */}
|
||||||
<div className="px-5 pb-3 flex items-center gap-3">
|
<div className="px-5 pb-3 flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -73,7 +73,20 @@ interface DropsTabProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function DropsTab({ showToast }: DropsTabProps) {
|
export function DropsTab({ showToast }: DropsTabProps) {
|
||||||
const openAnalyze = useAnalyzePanelStore((s) => s.open)
|
const openAnalyzePanel = useAnalyzePanelStore((s) => s.open)
|
||||||
|
|
||||||
|
// Wrapper to open analyze panel with drop status
|
||||||
|
const openAnalyze = useCallback((domain: string, item?: DroppedDomain) => {
|
||||||
|
if (item) {
|
||||||
|
openAnalyzePanel(domain, {
|
||||||
|
status: item.availability_status || 'unknown',
|
||||||
|
deletion_date: item.deletion_date,
|
||||||
|
is_drop: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
openAnalyzePanel(domain)
|
||||||
|
}
|
||||||
|
}, [openAnalyzePanel])
|
||||||
|
|
||||||
// Data State
|
// Data State
|
||||||
const [items, setItems] = useState<DroppedDomain[]>([])
|
const [items, setItems] = useState<DroppedDomain[]>([])
|
||||||
@ -104,6 +117,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
// Status Checking
|
// Status Checking
|
||||||
const [checkingStatus, setCheckingStatus] = useState<number | null>(null)
|
const [checkingStatus, setCheckingStatus] = useState<number | null>(null)
|
||||||
const [trackingDrop, setTrackingDrop] = useState<number | null>(null)
|
const [trackingDrop, setTrackingDrop] = useState<number | null>(null)
|
||||||
|
const [trackedDrops, setTrackedDrops] = useState<Set<number>>(new Set())
|
||||||
|
|
||||||
// Load Stats
|
// Load Stats
|
||||||
const loadStats = useCallback(async () => {
|
const loadStats = useCallback(async () => {
|
||||||
@ -214,20 +228,31 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
// Track a drop (add to watchlist)
|
// Track a drop (add to watchlist)
|
||||||
const trackDrop = useCallback(async (dropId: number, domain: string) => {
|
const trackDrop = useCallback(async (dropId: number, domain: string) => {
|
||||||
if (trackingDrop) return
|
if (trackingDrop) return
|
||||||
|
if (trackedDrops.has(dropId)) {
|
||||||
|
showToast(`${domain} is already in your Watchlist`, 'info')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setTrackingDrop(dropId)
|
setTrackingDrop(dropId)
|
||||||
try {
|
try {
|
||||||
const result = await api.trackDrop(dropId)
|
const result = await api.trackDrop(dropId)
|
||||||
|
// Mark as tracked regardless of status
|
||||||
|
setTrackedDrops(prev => new Set(prev).add(dropId))
|
||||||
|
|
||||||
if (result.status === 'already_tracking') {
|
if (result.status === 'already_tracking') {
|
||||||
showToast(`${domain} is already in your Watchlist`, 'info')
|
showToast(`${domain} is already in your Watchlist`, 'info')
|
||||||
} else {
|
} else {
|
||||||
showToast(result.message, 'success')
|
showToast(result.message || `Added ${domain} to Watchlist!`, 'success')
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(e instanceof Error ? e.message : 'Failed to track', 'error')
|
showToast(e instanceof Error ? e.message : 'Failed to track', 'error')
|
||||||
} finally {
|
} finally {
|
||||||
setTrackingDrop(null)
|
setTrackingDrop(null)
|
||||||
}
|
}
|
||||||
}, [trackingDrop, showToast])
|
}, [trackingDrop, trackedDrops, showToast])
|
||||||
|
|
||||||
|
// Check if a drop is already tracked
|
||||||
|
const isTracked = useCallback((dropId: number) => trackedDrops.has(dropId), [trackedDrops])
|
||||||
|
|
||||||
// Filtered and Sorted Items
|
// Filtered and Sorted Items
|
||||||
const sortedItems = useMemo(() => {
|
const sortedItems = useMemo(() => {
|
||||||
@ -557,6 +582,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
const fullDomain = `${item.domain}.${item.tld}`
|
const fullDomain = `${item.domain}.${item.tld}`
|
||||||
const isChecking = checkingStatus === item.id
|
const isChecking = checkingStatus === item.id
|
||||||
const isTrackingThis = trackingDrop === item.id
|
const isTrackingThis = trackingDrop === item.id
|
||||||
|
const alreadyTracked = isTracked(item.id)
|
||||||
const status = item.availability_status || 'unknown'
|
const status = item.availability_status || 'unknown'
|
||||||
|
|
||||||
// Status display config with better labels
|
// Status display config with better labels
|
||||||
@ -605,7 +631,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
<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(fullDomain)}
|
onClick={() => openAnalyze(fullDomain, item)}
|
||||||
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"
|
||||||
>
|
>
|
||||||
{item.domain}<span className="text-white/30">.{item.tld}</span>
|
{item.domain}<span className="text-white/30">.{item.tld}</span>
|
||||||
@ -635,14 +661,20 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{/* Track Button - always visible */}
|
{/* Track Button - shows "Tracked" if already in watchlist */}
|
||||||
<button
|
<button
|
||||||
onClick={() => trackDrop(item.id, fullDomain)}
|
onClick={() => trackDrop(item.id, fullDomain)}
|
||||||
disabled={isTrackingThis}
|
disabled={isTrackingThis || alreadyTracked}
|
||||||
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"
|
className={clsx(
|
||||||
|
"h-12 px-4 border text-xs font-bold uppercase tracking-widest flex items-center justify-center gap-2 transition-all",
|
||||||
|
alreadyTracked
|
||||||
|
? "border-accent/30 text-accent bg-accent/5 cursor-default"
|
||||||
|
: "border-white/10 text-white/60 hover:bg-white/5 active:scale-[0.98]"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{isTrackingThis ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
|
{isTrackingThis ? <Loader2 className="w-4 h-4 animate-spin" /> :
|
||||||
Track
|
alreadyTracked ? <CheckCircle2 className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||||
|
{alreadyTracked ? 'Tracked' : 'Track'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Action Button based on status */}
|
{/* Action Button based on status */}
|
||||||
@ -682,7 +714,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => openAnalyze(fullDomain)}
|
onClick={() => openAnalyze(fullDomain, item)}
|
||||||
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 +727,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
{/* Domain */}
|
{/* Domain */}
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => openAnalyze(fullDomain)}
|
onClick={() => openAnalyze(fullDomain, item)}
|
||||||
className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left block"
|
className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left block"
|
||||||
>
|
>
|
||||||
{item.domain}<span className="text-white/30 group-hover:text-accent/40">.{item.tld}</span>
|
{item.domain}<span className="text-white/30 group-hover:text-accent/40">.{item.tld}</span>
|
||||||
@ -739,17 +771,23 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* 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 */}
|
{/* Track Button - shows checkmark if tracked */}
|
||||||
<button
|
<button
|
||||||
onClick={() => trackDrop(item.id, fullDomain)}
|
onClick={() => trackDrop(item.id, fullDomain)}
|
||||||
disabled={isTrackingThis}
|
disabled={isTrackingThis || alreadyTracked}
|
||||||
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"
|
className={clsx(
|
||||||
title="Add to Watchlist"
|
"w-9 h-9 flex items-center justify-center border transition-all",
|
||||||
|
alreadyTracked
|
||||||
|
? "border-accent/30 text-accent bg-accent/5 cursor-default"
|
||||||
|
: "border-white/10 text-white/50 hover:text-white hover:bg-white/5"
|
||||||
|
)}
|
||||||
|
title={alreadyTracked ? "Already in Watchlist" : "Add to Watchlist"}
|
||||||
>
|
>
|
||||||
{isTrackingThis ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-3.5 h-3.5" />}
|
{isTrackingThis ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> :
|
||||||
|
alreadyTracked ? <CheckCircle2 className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => openAnalyze(fullDomain)}
|
onClick={() => openAnalyze(fullDomain, item)}
|
||||||
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"
|
||||||
title="Analyze Domain"
|
title="Analyze Domain"
|
||||||
>
|
>
|
||||||
@ -769,15 +807,22 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
Buy
|
Buy
|
||||||
</a>
|
</a>
|
||||||
) : status === 'dropping_soon' ? (
|
) : status === 'dropping_soon' ? (
|
||||||
<button
|
alreadyTracked ? (
|
||||||
onClick={() => trackDrop(item.id, fullDomain)}
|
<span className="h-9 px-3 text-accent text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 border border-accent/30 bg-accent/5">
|
||||||
disabled={isTrackingThis}
|
<CheckCircle2 className="w-3 h-3" />
|
||||||
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"
|
Tracked
|
||||||
title={countdown ? `Drops in ${countdown} - Track to get notified!` : 'Track to get notified when available'}
|
</span>
|
||||||
>
|
) : (
|
||||||
{isTrackingThis ? <Loader2 className="w-3 h-3 animate-spin" /> : <Eye className="w-3 h-3" />}
|
<button
|
||||||
Track
|
onClick={() => trackDrop(item.id, fullDomain)}
|
||||||
</button>
|
disabled={isTrackingThis}
|
||||||
|
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" />
|
||||||
|
|||||||
@ -2,17 +2,25 @@ import { create } from 'zustand'
|
|||||||
|
|
||||||
export type AnalyzeSectionVisibility = Record<string, boolean>
|
export type AnalyzeSectionVisibility = Record<string, boolean>
|
||||||
|
|
||||||
|
export type DropStatusInfo = {
|
||||||
|
status: 'available' | 'dropping_soon' | 'taken' | 'unknown'
|
||||||
|
deletion_date?: string | null
|
||||||
|
is_drop?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type AnalyzePanelState = {
|
export type AnalyzePanelState = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
domain: string | null
|
domain: string | null
|
||||||
fastMode: boolean
|
fastMode: boolean
|
||||||
filterText: string
|
filterText: string
|
||||||
sectionVisibility: AnalyzeSectionVisibility
|
sectionVisibility: AnalyzeSectionVisibility
|
||||||
open: (domain: string) => void
|
dropStatus: DropStatusInfo | null
|
||||||
|
open: (domain: string, dropStatus?: DropStatusInfo) => void
|
||||||
close: () => void
|
close: () => void
|
||||||
setFastMode: (fast: boolean) => void
|
setFastMode: (fast: boolean) => void
|
||||||
setFilterText: (value: string) => void
|
setFilterText: (value: string) => void
|
||||||
setSectionVisibility: (next: AnalyzeSectionVisibility) => void
|
setSectionVisibility: (next: AnalyzeSectionVisibility) => void
|
||||||
|
setDropStatus: (status: DropStatusInfo | null) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_VISIBILITY: AnalyzeSectionVisibility = {
|
const DEFAULT_VISIBILITY: AnalyzeSectionVisibility = {
|
||||||
@ -28,11 +36,13 @@ export const useAnalyzePanelStore = create<AnalyzePanelState>((set) => ({
|
|||||||
fastMode: false,
|
fastMode: false,
|
||||||
filterText: '',
|
filterText: '',
|
||||||
sectionVisibility: DEFAULT_VISIBILITY,
|
sectionVisibility: DEFAULT_VISIBILITY,
|
||||||
open: (domain) => set({ isOpen: true, domain, filterText: '' }),
|
dropStatus: null,
|
||||||
close: () => set({ isOpen: false }),
|
open: (domain, dropStatus) => set({ isOpen: true, domain, filterText: '', dropStatus: dropStatus || null }),
|
||||||
|
close: () => set({ isOpen: false, dropStatus: null }),
|
||||||
setFastMode: (fastMode) => set({ fastMode }),
|
setFastMode: (fastMode) => set({ fastMode }),
|
||||||
setFilterText: (filterText) => set({ filterText }),
|
setFilterText: (filterText) => set({ filterText }),
|
||||||
setSectionVisibility: (sectionVisibility) => set({ sectionVisibility }),
|
setSectionVisibility: (sectionVisibility) => set({ sectionVisibility }),
|
||||||
|
setDropStatus: (dropStatus) => set({ dropStatus }),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export const ANALYZE_PREFS_KEY = 'pounce_analyze_prefs_v1'
|
export const ANALYZE_PREFS_KEY = 'pounce_analyze_prefs_v1'
|
||||||
|
|||||||
Reference in New Issue
Block a user