238 lines
7.3 KiB
TypeScript
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}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|