import { useState, useMemo } from 'react' import { useMutation, useQueryClient } from '@tanstack/react-query' import { Trash2, ChevronUp, ChevronDown } from 'lucide-react' import { useCertificates } from '../hooks/useCertificates' import { deleteCertificate } from '../api/certificates' import { LoadingSpinner } from './LoadingStates' import { toast } from '../utils/toast' type SortColumn = 'name' | 'expires' type SortDirection = 'asc' | 'desc' export default function CertificateList() { const { certificates, isLoading, error } = useCertificates() const queryClient = useQueryClient() const [sortColumn, setSortColumn] = useState('name') const [sortDirection, setSortDirection] = useState('asc') const deleteMutation = useMutation({ mutationFn: deleteCertificate, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['certificates'] }) toast.success('Certificate deleted') }, onError: (error: any) => { toast.error(`Failed to delete certificate: ${error.message}`) }, }) const sortedCertificates = useMemo(() => { return [...certificates].sort((a, b) => { let comparison = 0 switch (sortColumn) { case 'name': { const aName = (a.name || a.domain || '').toLowerCase() const bName = (b.name || b.domain || '').toLowerCase() comparison = aName.localeCompare(bName) break } case 'expires': { const aDate = new Date(a.expires_at).getTime() const bDate = new Date(b.expires_at).getTime() comparison = aDate - bDate break } } return sortDirection === 'asc' ? comparison : -comparison }) }, [certificates, sortColumn, sortDirection]) const handleSort = (column: SortColumn) => { if (sortColumn === column) { setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc') } else { setSortColumn(column) setSortDirection('asc') } } const SortIcon = ({ column }: { column: SortColumn }) => { if (sortColumn !== column) return null return sortDirection === 'asc' ? : } if (isLoading) return if (error) return
Failed to load certificates
return (
{certificates.length === 0 ? ( ) : ( sortedCertificates.map((cert) => ( )) )}
handleSort('name')} className="px-6 py-3 cursor-pointer hover:text-white transition-colors" >
Name
Domain Issuer handleSort('expires')} className="px-6 py-3 cursor-pointer hover:text-white transition-colors" >
Expires
Status Actions
No certificates found.
{cert.name || '-'} {cert.domain}
{cert.issuer} {cert.issuer?.toLowerCase().includes('staging') && ( STAGING )}
{new Date(cert.expires_at).toLocaleDateString()} {cert.id && (cert.provider === 'custom' || cert.issuer?.includes('staging')) && ( )}
) } function StatusBadge({ status }: { status: string }) { const styles = { valid: 'bg-green-900/30 text-green-400 border-green-800', expiring: 'bg-yellow-900/30 text-yellow-400 border-yellow-800', expired: 'bg-red-900/30 text-red-400 border-red-800', untrusted: 'bg-orange-900/30 text-orange-400 border-orange-800', } const labels = { valid: 'Valid', expiring: 'Expiring Soon', expired: 'Expired', untrusted: 'Untrusted (Staging)', } const style = styles[status as keyof typeof styles] || styles.valid const label = labels[status as keyof typeof labels] || status return ( {label} ) }