"use client"
import { useState, useMemo } from "react"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { useToast } from "@/components/ui/use-toast"
import { Trans, useLingui } from "@lingui/react/macro"
import { Button } from "@/components/ui/button"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "@/components/ui/card"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
DropdownMenuCheckboxItem,
} from "@/components/ui/dropdown-menu"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import {
getDomains,
deleteDomain,
refreshDomain,
getStatusBadgeColor,
getStatusLabel,
formatDate,
type Domain,
} from "@/lib/domains"
import {
MoreHorizontal,
Plus,
RefreshCw,
Globe,
AlertTriangle,
CheckCircle2,
Clock,
FilterIcon,
LayoutGridIcon,
LayoutListIcon,
Tag,
} from "lucide-react"
import { DomainDialog } from "./domain-dialog"
import { Link } from "@/components/router"
import { useBrowserStorage } from "@/lib/utils"
type ViewMode = "table" | "grid"
type StatusFilter = "all" | "active" | "expiring" | "expired" | "unknown" | "paused"
type DisplayOptions = {
showSSL: boolean
showRegistrar: boolean
showExpiryDate: boolean
showTags: boolean
}
// Days left badge component - big and visible
function DaysLeftBadge({ days, label = "days" }: { days: number | undefined; label?: string }) {
if (days === undefined || days === null) return -
const isCritical = days >= 0 && days <= 7
const isWarning = days >= 0 && days <= 30
const isExpired = days < 0
const colorClass = isExpired
? "bg-red-500/15 text-red-600 border-red-500/30"
: isCritical
? "bg-red-500/15 text-red-600 border-red-500/30"
: isWarning
? "bg-yellow-500/15 text-yellow-600 border-yellow-500/30"
: "bg-green-500/15 text-green-600 border-green-500/30"
return (
{isExpired ? Math.abs(days) : days}
{isExpired ? "EXPIRED" : days === 1 ? "DAY" : label.toUpperCase()}
)
}
export default function DomainsTable() {
const { t } = useLingui()
const { toast } = useToast()
const queryClient = useQueryClient()
const [dialogOpen, setDialogOpen] = useState(false)
const [editingDomain, setEditingDomain] = useState(null)
const [deleteConfirmId, setDeleteConfirmId] = useState(null)
const [filter, setFilter] = useState("")
const [statusFilter, setStatusFilter] = useState("all")
const [tagFilter, setTagFilter] = useState("all")
const [viewMode, setViewMode] = useBrowserStorage(
"domainsViewMode",
window.innerWidth < 1024 ? "grid" : "table"
)
const [displayOptions, setDisplayOptions] = useBrowserStorage(
"domainsDisplayOptions",
{ showSSL: true, showRegistrar: true, showExpiryDate: true, showTags: true }
)
const { data: domains = [], isLoading } = useQuery({
queryKey: ["domains"],
queryFn: getDomains,
})
// Filter by status first
const statusFilteredDomains = useMemo(() => {
if (statusFilter === "all") return domains
return domains.filter((d) => d.status === statusFilter)
}, [domains, statusFilter])
// Then filter by search text and tags
const filteredDomains = useMemo(() => {
let result = statusFilteredDomains
if (filter) {
const f = filter.toLowerCase()
result = result.filter(
(d) =>
d.domain_name.toLowerCase().includes(f) ||
(d.registrar_name || "").toLowerCase().includes(f)
)
}
if (tagFilter !== "all") {
result = result.filter((d) => d.tags?.includes(tagFilter))
}
return result
}, [statusFilteredDomains, filter, tagFilter])
// Extract all unique tags
const allTags = useMemo(() => {
const tagSet = new Set()
domains.forEach((d) => d.tags?.forEach((tag) => tagSet.add(tag)))
return Array.from(tagSet).sort()
}, [domains])
const statusCounts = useMemo(() => {
const total = domains.length
const active = domains.filter((d) => d.status === "active").length
const expiring = domains.filter((d) => d.status === "expiring").length
const expired = domains.filter((d) => d.status === "expired").length
const unknown = domains.filter((d) => d.status === "unknown").length
const paused = domains.filter((d) => d.status === "paused").length
return { total, active, expiring, expired, unknown, paused }
}, [domains])
const deleteMutation = useMutation({
mutationFn: deleteDomain,
onSuccess: () => {
toast({ title: "Domain deleted successfully" })
queryClient.invalidateQueries({ queryKey: ["domains"] })
},
})
const refreshMutation = useMutation({
mutationFn: refreshDomain,
onSuccess: () => {
toast({ title: "Domain refresh started" })
queryClient.invalidateQueries({ queryKey: ["domains"] })
},
})
const handleEdit = (domain: Domain) => {
setEditingDomain(domain)
setDialogOpen(true)
}
const handleAdd = () => {
setEditingDomain(null)
setDialogOpen(true)
}
const handleDelete = (id: string) => {
setDeleteConfirmId(id)
}
const handleRefresh = (id: string) => {
refreshMutation.mutate(id)
}
const getStatusIcon = (status: string) => {
switch (status) {
case "active":
return
case "expiring":
return
case "expired":
return
default:
return
}
}
if (isLoading) {
return (
Loading...
)
}
return (
<>
{/* Title row */}
Domain Monitoring
Track domain expiry dates and watch domains for purchase
({statusCounts.active}
{statusCounts.expiring > 0 && (
<>
{" "}
{statusCounts.expiring}{" "}
>
)}
{statusCounts.expired > 0 && (
<>
{" "}
{statusCounts.expired}{" "}
>
)}
{statusCounts.paused > 0 && (
<>
{" "}
{statusCounts.paused}{" "}
>
)}
/ {statusCounts.total})
Add Domain
{/* Quick status filters */}
{[
{ key: "all", label: `All ${statusCounts.total}`, color: "bg-primary" },
{ key: "active", label: `Active ${statusCounts.active}`, color: "bg-green-500" },
{ key: "expiring", label: `Expiring ${statusCounts.expiring}`, color: "bg-yellow-500" },
{ key: "expired", label: `Expired ${statusCounts.expired}`, color: "bg-red-500" },
{ key: "unknown", label: `Unknown ${statusCounts.unknown}`, color: "bg-gray-400" },
{ key: "paused", label: `Paused ${statusCounts.paused}`, color: "bg-gray-400" },
].map((s) => (
setStatusFilter(s.key as StatusFilter)}
disabled={s.key !== "all" && parseInt(s.label.split(" ")[1]) === 0}
>
{s.label.split(" ")[0]}
{s.label.split(" ")[1]}
))}
{/* Filter row */}
setFilter(e.target.value)}
value={filter}
className="w-full"
/>
{allTags.length > 0 && (
{tagFilter === "all" ? t`Tags` : tagFilter}
All Tags
{allTags.map((tag) => (
{tag}
))}
)}
Options
{/* Layout */}
Layout
setViewMode(v as ViewMode)}>
Table
Grid
{/* Status Filter */}
Status
setStatusFilter(v as StatusFilter)}>
All ({statusCounts.total})
Active ({statusCounts.active})
Expiring ({statusCounts.expiring})
Expired ({statusCounts.expired})
Unknown ({statusCounts.unknown})
{statusCounts.paused > 0 && (
Paused ({statusCounts.paused})
)}
{/* Display Options */}
Display Columns
setDisplayOptions({ ...displayOptions, showSSL: checked })}
>
SSL Info
setDisplayOptions({ ...displayOptions, showRegistrar: checked })}
>
Registrar
setDisplayOptions({ ...displayOptions, showExpiryDate: checked })}
>
Expiry Date
setDisplayOptions({ ...displayOptions, showTags: checked })}
>
Tags
{filteredDomains.length === 0 ? (
{filter || statusFilter !== "all" ? (
"No domains match your filters."
) : (
No domains tracked. Add domains to monitor their expiry dates or track domains you want to buy.
Add your first domain
)}
) : viewMode === "table" ? (
Domain
Status
{displayOptions.showExpiryDate && Expiry }
Days Left
{displayOptions.showRegistrar && Registrar }
{displayOptions.showSSL && SSL Expiry }
{displayOptions.showTags && Tags }
Actions
{filteredDomains.map((domain) => (
{domain.favicon_url && (
(e.currentTarget.style.display = "none")}
/>
)}
{domain.domain_name}
{getStatusIcon(domain.status)}
{getStatusLabel(domain.status)}
{displayOptions.showExpiryDate && (
{domain.expiry_date ? formatDate(domain.expiry_date) : "Unknown"}
)}
{displayOptions.showRegistrar && (
{domain.registrar_name || "Unknown"}
)}
{displayOptions.showSSL && (
{domain.ssl_valid_to ? (
) : (
-
)}
)}
{displayOptions.showTags && (
{domain.tags?.map((tag: string) => (
{tag}
))}
)}
handleEdit(domain)}>
Edit
handleRefresh(domain.id)}
disabled={refreshMutation.isPending}
>
Refresh
Visit
handleDelete(domain.id)}
className="text-destructive"
>
Delete
))}
) : (
{filteredDomains.map((domain) => (
{domain.favicon_url && (
(e.currentTarget.style.display = "none")}
/>
)}
handleEdit(domain)}>Edit
handleRefresh(domain.id)} disabled={refreshMutation.isPending}>
Refresh
handleDelete(domain.id)} className="text-destructive">
Delete
{getStatusIcon(domain.status)}
{getStatusLabel(domain.status)}
{displayOptions.showTags && domain.tags && domain.tags.length > 0 && (
{domain.tags.map((tag: string) => (
{tag}
))}
)}
{displayOptions.showExpiryDate && (
{domain.expiry_date ? formatDate(domain.expiry_date) : "Unknown"}
)}
{displayOptions.showRegistrar && (
{domain.registrar_name || "Unknown"}
)}
{displayOptions.showSSL && domain.ssl_valid_to && (
)}
))}
)}
setDeleteConfirmId(null)}>
Delete Domain
Are you sure you want to delete this domain? This action cannot be undone.
Cancel
{
if (deleteConfirmId) {
deleteMutation.mutate(deleteConfirmId)
setDeleteConfirmId(null)
}
}}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Delete
>
)
}