vultr-dns-manager/components/domains-list.tsx
2025-03-16 07:38:30 -06:00

238 lines
7.3 KiB
TypeScript

"use client";
import { useEffect, useState } from 'react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Button } from '@/components/ui/button';
import { Skeleton } from '@/components/ui/skeleton';
import { Input } from '@/components/ui/input';
import { useToast } from '@/hooks/use-toast';
import { Globe, Plus, Settings, Search, X } from 'lucide-react';
import { AddDomainDialog } from './add-domain-dialog';
import { DomainSettingsDialog } from './domain-settings-dialog';
interface Domain {
domain: string;
date_created: string;
dnssec?: "enabled" | "disabled";
}
interface DomainsListProps {
apiKey: string;
onSelectDomain: (domain: string) => void;
selectedDomain: string | null;
}
export function DomainsList({ apiKey, onSelectDomain, selectedDomain }: DomainsListProps) {
const [domains, setDomains] = useState<Domain[]>([]);
const [filteredDomains, setFilteredDomains] = useState<Domain[]>([]);
const [searchQuery, setSearchQuery] = useState('');
const [loading, setLoading] = useState(true);
const [isAddDomainOpen, setIsAddDomainOpen] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const { toast } = useToast();
const fetchDomains = async () => {
try {
setLoading(true);
const response = await fetch('/api/vultr/domains', {
headers: {
'X-API-Key': apiKey,
},
});
if (!response.ok) {
throw new Error('Failed to fetch domains');
}
const data = await response.json();
setDomains(data.domains || []);
setFilteredDomains(data.domains || []);
} catch (error) {
console.error('Error fetching domains:', error);
toast({
title: 'Error',
description: 'Failed to fetch domains. Please check your API key.',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchDomains();
}, [apiKey, toast]);
// Filter domains when search query changes
useEffect(() => {
if (searchQuery.trim() === '') {
setFilteredDomains(domains);
} else {
const query = searchQuery.toLowerCase();
const filtered = domains.filter(domain =>
domain.domain.toLowerCase().includes(query)
);
setFilteredDomains(filtered);
}
}, [searchQuery, domains]);
const handleDomainAdded = (newDomain: Domain) => {
const updatedDomains = [newDomain, ...domains];
setDomains(updatedDomains);
setFilteredDomains(updatedDomains);
toast({
title: 'Domain Added',
description: `${newDomain.domain} has been added successfully.`,
});
setIsAddDomainOpen(false);
};
const handleDomainUpdated = (updatedDomain: Domain) => {
const updatedDomains = domains.map(domain =>
domain.domain === updatedDomain.domain ? updatedDomain : domain
);
setDomains(updatedDomains);
setFilteredDomains(updatedDomains);
toast({
title: 'Domain Updated',
description: `${updatedDomain.domain} settings have been updated successfully.`,
});
setIsSettingsOpen(false);
};
const clearSearch = () => {
setSearchQuery('');
};
if (loading) {
return (
<div className="space-y-2">
<Skeleton className="h-10 w-full mb-4" />
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={i} className="h-12 w-full" />
))}
</div>
);
}
const selectedDomainData = selectedDomain
? domains.find(d => d.domain === selectedDomain)
: null;
return (
<>
<div className="mb-4 space-y-2">
<div className="relative">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="Search domains..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 pr-9"
/>
{searchQuery && (
<Button
variant="ghost"
size="sm"
className="absolute right-1 top-1/2 h-7 w-7 -translate-y-1/2 p-0"
onClick={clearSearch}
>
<X className="h-4 w-4" />
<span className="sr-only">Clear search</span>
</Button>
)}
</div>
<Button
variant="outline"
className="w-full"
onClick={() => setIsAddDomainOpen(true)}
>
<Plus className="h-4 w-4 mr-2" />
Add Domain
</Button>
{selectedDomain && (
<Button
variant="outline"
className="w-full"
onClick={() => setIsSettingsOpen(true)}
>
<Settings className="h-4 w-4 mr-2" />
Domain Settings
</Button>
)}
</div>
{domains.length === 0 ? (
<div className="text-center py-8">
<Globe className="mx-auto h-12 w-12 text-muted-foreground opacity-50" />
<h3 className="mt-4 text-lg font-semibold">No domains found</h3>
<p className="text-sm text-muted-foreground mt-2">
You don't have any domains configured in your Vultr account.
</p>
</div>
) : filteredDomains.length === 0 ? (
<div className="text-center py-8">
<Search className="mx-auto h-12 w-12 text-muted-foreground opacity-50" />
<h3 className="mt-4 text-lg font-semibold">No matching domains</h3>
<p className="text-sm text-muted-foreground mt-2">
No domains match your search query "{searchQuery}"
</p>
<Button
variant="link"
onClick={clearSearch}
className="mt-2"
>
Clear search
</Button>
</div>
) : (
<ScrollArea className="h-[calc(100vh-350px)]">
<div className="space-y-2 pr-4">
{filteredDomains.map((domain) => (
<Button
key={domain.domain}
variant={selectedDomain === domain.domain ? "default" : "outline"}
className="w-full justify-start text-left font-normal h-auto py-3"
onClick={() => onSelectDomain(domain.domain)}
>
<div className="flex flex-col items-start">
<span className="font-medium">{domain.domain}</span>
<span className="text-xs text-muted-foreground mt-1">
Created: {new Date(domain.date_created).toLocaleDateString()}
</span>
{domain.dnssec && (
<span className="text-xs mt-1">
DNSSEC: <span className={domain.dnssec === "enabled" ? "text-green-500" : "text-muted-foreground"}>
{domain.dnssec}
</span>
</span>
)}
</div>
</Button>
))}
</div>
</ScrollArea>
)}
<AddDomainDialog
open={isAddDomainOpen}
onOpenChange={setIsAddDomainOpen}
apiKey={apiKey}
onDomainAdded={handleDomainAdded}
/>
{selectedDomainData && (
<DomainSettingsDialog
open={isSettingsOpen}
onOpenChange={setIsSettingsOpen}
apiKey={apiKey}
domain={selectedDomainData}
onDomainUpdated={handleDomainUpdated}
/>
)}
</>
);
}