'use client' import { useEffect, useState, useCallback } from 'react' import { useStore } from '@/lib/store' import { api } from '@/lib/api' import { TerminalLayout } from '@/components/TerminalLayout' import { Plus, Target, Zap, Edit2, Trash2, Power, PowerOff, Eye, Bell, MessageSquare, Loader2, X, AlertCircle, CheckCircle, TrendingUp, Filter, Clock, DollarSign, Hash, Tag, Crown, Activity } from 'lucide-react' import clsx from 'clsx' import Link from 'next/link' // ============================================================================ // SHARED COMPONENTS // ============================================================================ const StatCard = ({ label, value, subValue, icon: Icon, highlight, trend }: { label: string value: string | number subValue?: string icon: any highlight?: boolean trend?: 'up' | 'down' | 'neutral' | 'active' }) => (
{label}
{value} {subValue && {subValue}}
{highlight && (
● LIVE
)}
) // ============================================================================ // INTERFACES // ============================================================================ interface SniperAlert { id: number name: string description: string | null tlds: string | null keywords: string | null exclude_keywords: string | null max_length: number | null min_length: number | null max_price: number | null min_price: number | null max_bids: number | null ending_within_hours: number | null platforms: string | null no_numbers: boolean no_hyphens: boolean exclude_chars: string | null notify_email: boolean notify_sms: boolean is_active: boolean matches_count: number notifications_sent: number last_matched_at: string | null created_at: string } // ============================================================================ // MAIN PAGE // ============================================================================ export default function SniperAlertsPage() { const { subscription } = useStore() const [alerts, setAlerts] = useState([]) const [loading, setLoading] = useState(true) const [showCreateModal, setShowCreateModal] = useState(false) const [editingAlert, setEditingAlert] = useState(null) const [deletingId, setDeletingId] = useState(null) const [togglingId, setTogglingId] = useState(null) // Tier-based limits const tier = subscription?.tier || 'scout' const alertLimits: Record = { scout: 2, trader: 10, tycoon: 50 } const maxAlerts = alertLimits[tier] || 2 const canAddMore = alerts.length < maxAlerts const isTycoon = tier === 'tycoon' // Stats const activeAlerts = alerts.filter(a => a.is_active).length const totalMatches = alerts.reduce((sum, a) => sum + a.matches_count, 0) const totalNotifications = alerts.reduce((sum, a) => sum + a.notifications_sent, 0) // Load alerts const loadAlerts = useCallback(async () => { setLoading(true) try { const data = await api.request('/sniper-alerts') setAlerts(data) } catch (err) { console.error('Failed to load alerts:', err) } finally { setLoading(false) } }, []) useEffect(() => { loadAlerts() }, [loadAlerts]) // Toggle alert active status const handleToggle = async (id: number, currentStatus: boolean) => { setTogglingId(id) try { await api.request(`/sniper-alerts/${id}`, { method: 'PUT', body: JSON.stringify({ is_active: !currentStatus }), }) await loadAlerts() } catch (err: any) { alert(err.message || 'Failed to toggle alert') } finally { setTogglingId(null) } } // Delete alert const handleDelete = async (id: number, name: string) => { if (!confirm(`Delete alert "${name}"?`)) return setDeletingId(id) try { await api.request(`/sniper-alerts/${id}`, { method: 'DELETE' }) await loadAlerts() } catch (err: any) { alert(err.message || 'Failed to delete alert') } finally { setDeletingId(null) } } return (
{/* Ambient Background Glow */}
{/* Content */}
{/* Header */}

Sniper Alerts

Get notified when domains matching your exact criteria hit the market. Set it, forget it, and pounce when the time is right.

{/* Stats */}
0} /> 0 ? 'up' : 'neutral'} /> 0 ? 'up' : 'neutral'} />
{/* Alerts List */}
{/* Header */}

Your Alerts

{alerts.length} / {maxAlerts}
{/* Loading */} {loading && (
)} {/* Empty State */} {!loading && alerts.length === 0 && (

No Alerts Yet

Create your first sniper alert to get notified when domains matching your criteria appear in auctions.

)} {/* Alerts Grid */} {!loading && alerts.length > 0 && (
{alerts.map((alert) => (
{/* Alert Info */}

{alert.name}

{alert.is_active ? (
ACTIVE ) : ( PAUSED )} {isTycoon && alert.notify_sms && ( SMS )}
{alert.description && (

{alert.description}

)} {/* Criteria Pills */}
{alert.tlds && ( {alert.tlds} )} {alert.keywords && ( +{alert.keywords} )} {alert.exclude_keywords && ( -{alert.exclude_keywords} )} {(alert.min_length || alert.max_length) && ( {alert.min_length || 1}-{alert.max_length || 63} chars )} {(alert.min_price || alert.max_price) && ( {alert.min_price ? `$${alert.min_price}+` : ''}{alert.max_price ? ` - $${alert.max_price}` : ''} )} {alert.no_numbers && ( No numbers )} {alert.no_hyphens && ( No hyphens )}
{/* Stats */}
{alert.matches_count} matches {alert.notifications_sent} sent {alert.last_matched_at && ( Last: {new Date(alert.last_matched_at).toLocaleDateString()} )}
{/* Actions */}
))}
)}
{/* Upgrade CTA */} {!canAddMore && (

Alert Limit Reached

You've created {maxAlerts} alerts. Upgrade to add more.

Trader: 10 alerts
Tycoon: 50 alerts + SMS
Upgrade Now
)}
{/* Create/Edit Modal */} {(showCreateModal || editingAlert) && ( { setShowCreateModal(false) setEditingAlert(null) }} onSuccess={() => { loadAlerts() setShowCreateModal(false) setEditingAlert(null) }} isTycoon={isTycoon} /> )}
) } // ============================================================================ // CREATE/EDIT MODAL // ============================================================================ function CreateEditModal({ alert, onClose, onSuccess, isTycoon }: { alert: SniperAlert | null onClose: () => void onSuccess: () => void isTycoon: boolean }) { const isEditing = !!alert const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [form, setForm] = useState({ name: alert?.name || '', description: alert?.description || '', tlds: alert?.tlds || '', keywords: alert?.keywords || '', exclude_keywords: alert?.exclude_keywords || '', min_length: alert?.min_length?.toString() || '', max_length: alert?.max_length?.toString() || '', min_price: alert?.min_price?.toString() || '', max_price: alert?.max_price?.toString() || '', max_bids: alert?.max_bids?.toString() || '', ending_within_hours: alert?.ending_within_hours?.toString() || '', platforms: alert?.platforms || '', no_numbers: alert?.no_numbers || false, no_hyphens: alert?.no_hyphens || false, exclude_chars: alert?.exclude_chars || '', notify_email: alert?.notify_email ?? true, notify_sms: alert?.notify_sms || false, }) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setLoading(true) setError(null) try { const payload = { name: form.name, description: form.description || null, tlds: form.tlds || null, keywords: form.keywords || null, exclude_keywords: form.exclude_keywords || null, min_length: form.min_length ? parseInt(form.min_length) : null, max_length: form.max_length ? parseInt(form.max_length) : null, min_price: form.min_price ? parseFloat(form.min_price) : null, max_price: form.max_price ? parseFloat(form.max_price) : null, max_bids: form.max_bids ? parseInt(form.max_bids) : null, ending_within_hours: form.ending_within_hours ? parseInt(form.ending_within_hours) : null, platforms: form.platforms || null, no_numbers: form.no_numbers, no_hyphens: form.no_hyphens, exclude_chars: form.exclude_chars || null, notify_email: form.notify_email, notify_sms: form.notify_sms && isTycoon, } if (isEditing) { await api.request(`/sniper-alerts/${alert.id}`, { method: 'PUT', body: JSON.stringify(payload), }) } else { await api.request('/sniper-alerts', { method: 'POST', body: JSON.stringify(payload), }) } onSuccess() } catch (err: any) { setError(err.message || 'Failed to save alert') } finally { setLoading(false) } } return (
e.stopPropagation()} > {/* Header */}

{isEditing ? 'Edit Alert' : 'Create Sniper Alert'}

Set precise criteria for domain matching

{error && (

{error}

)} {/* Basic Info */}

Basic Info

setForm({ ...form, name: e.target.value })} placeholder="e.g. Premium 4L .com domains" required className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all" />