'use client'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { Plus, RefreshCw, Route, Trash2 } from 'lucide-react'; import { toast } from 'sonner'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Skeleton } from '@/components/ui/skeleton'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import type { ProxyRouteItem } from '@/lib/services/openflare'; import { ProxyRouteService } from '@/lib/services/openflare'; import { useTranslations } from 'next-intl'; import { getRouteDomainNames, getRouteDomainsLabel, getRoutePrimaryDomain, getUpstreamSummary, } from './components/helpers'; import { ProxyRouteCreateSheet } from './components/proxy-route-create-sheet'; export function ProxyRoutesPageClient() { const t = useTranslations('proxyRoutes'); const tc = useTranslations('common'); const router = useRouter(); const [routes, setRoutes] = useState([]); const [loading, setLoading] = useState(true); const [keyword, setKeyword] = useState(''); const [isCreateOpen, setIsCreateOpen] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [deleting, setDeleting] = useState(false); const fetchRoutes = useCallback(async () => { setLoading(true); try { const data = await ProxyRouteService.list(); setRoutes(data); } catch (error) { toast.error(t('loadListFailed'), { description: error instanceof Error ? error.message : t('unknownError'), }); } finally { setLoading(false); } }, [t]); useEffect(() => { void fetchRoutes(); }, [fetchRoutes]); const filteredRoutes = useMemo(() => { const normalizedKeyword = keyword.trim().toLowerCase(); if (!normalizedKeyword) { return routes; } return routes.filter((route) => { const haystack = [ route.site_name, getRoutePrimaryDomain(route), ...getRouteDomainNames(route), route.origin_url, ] .join(' ') .toLowerCase(); return haystack.includes(normalizedKeyword); }); }, [keyword, routes]); const handleDelete = async () => { if (!deleteTarget) { return; } setDeleting(true); try { await ProxyRouteService.deleteById(deleteTarget.id); toast.success(t('siteDeleted')); setDeleteTarget(null); await fetchRoutes(); } catch (error) { toast.error(t('deleteFailed'), { description: error instanceof Error ? error.message : t('unknownError'), }); } finally { setDeleting(false); } }; return (

{t('title')}

{t('listTitle')} setKeyword(event.target.value)} placeholder={t('searchPlaceholder')} className='h-8 max-w-sm text-xs' />
{loading ? (
{Array.from({ length: 5 }).map((_, index) => ( ))}
) : filteredRoutes.length === 0 ? (

{routes.length === 0 ? t('emptyTitle') : t('noMatches')}

{routes.length === 0 ? t('emptyDesc') : t('noMatchesDesc')}

) : ( {t('siteName')} {t('domain')} {t('status')} {t('upstream')} {t('actions')} {filteredRoutes.map((route) => ( {route.site_name} {getRoutePrimaryDomain(route) || getRouteDomainsLabel(route, t)} {route.enabled ? t('enabled') : t('disabled')} {getUpstreamSummary(route, t)}
))}
)}
{ toast.success(t('siteCreated')); void fetchRoutes(); router.push(`/proxy-routes/detail?id=${route.id}§ion=domains`); }} /> { if (!open) { setDeleteTarget(null); } }} > {t('deleteSiteTitle')} {deleteTarget ? t('deleteSiteDesc', { name: deleteTarget.site_name }) : null} {tc('cancel')} { event.preventDefault(); void handleDelete(); }} > {deleting ? t('deleting') : tc('delete')}
); }