'use client'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { ArrowLeft, Globe, Pencil, Trash2 } from 'lucide-react'; import { toast } from 'sonner'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Button } from '@/components/ui/button'; import { EmptyStateWithBorder } from '@/components/layout/empty'; import { LoadingStateWithBorder } from '@/components/layout/loading'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { ProxyRouteService, TlsCertificateService, ZoneService, zoneQueryKey, } from '@/lib/services/openflare'; import { useTranslations } from 'next-intl'; import { getErrorMessage } from '../components/website-utils'; import { ZoneCertificatesPanel } from './components/zone-certificates'; import { ZoneDomainsTable } from './components/zone-domains-table'; import { ZoneEditorDialog } from './components/zone-editor-dialog'; import { ZoneOverviewPanel } from './components/zone-overview'; const zoneTabs = ['overview', 'domains', 'certificates', 'settings'] as const; export type ZonePageTab = (typeof zoneTabs)[number]; function getZonePageTab(value: string | null | undefined): ZonePageTab { return zoneTabs.includes(value as ZonePageTab) ? (value as ZonePageTab) : 'overview'; } function getZoneIdFromPathname(pathname: string | null): number { const match = pathname?.match(/^\/websites\/([^/]+)$/); return Number(match?.[1]); } export function ZonePageClient() { const t = useTranslations('websites'); const tc = useTranslations('common'); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); const router = useRouter(); const pathname = usePathname(); const zoneId = useMemo(() => getZoneIdFromPathname(pathname), [pathname]); const searchParams = useSearchParams(); const queryClient = useQueryClient(); const activeTab = useMemo( () => getZonePageTab(searchParams.get('tab')), [searchParams], ); const [editZone, setEditZone] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const setActiveTab = useCallback( (tab: string) => { const next = getZonePageTab(tab); const params = new URLSearchParams(searchParams.toString()); if (next === 'overview') { params.delete('tab'); } else { params.set('tab', next); } const query = params.toString(); router.replace(query ? `${pathname}?${query}` : pathname, { scroll: false, }); }, [pathname, router, searchParams], ); const overviewQuery = useQuery({ queryKey: [...zoneQueryKey, zoneId], queryFn: () => ZoneService.getOverview(zoneId), enabled: Number.isInteger(zoneId) && zoneId > 0, }); const certificatesQuery = useQuery({ queryKey: ['openflare', 'tls-certificates'], queryFn: () => TlsCertificateService.list(), enabled: overviewQuery.isSuccess, }); const routesQuery = useQuery({ queryKey: ['openflare', 'proxy-routes'], queryFn: () => ProxyRouteService.list(), enabled: overviewQuery.isSuccess, }); const deleteZone = useMutation({ mutationFn: () => ZoneService.deleteById(zoneId), onSuccess: async () => { toast.success(t('zoneDeleted')); await queryClient.invalidateQueries({ queryKey: zoneQueryKey }); window.location.assign('/websites'); }, onError: (error) => toast.error(getErrorMessage(error, t('requestFailed'))), }); if (!mounted) { return (
); } if (!Number.isInteger(zoneId) || zoneId <= 0) { return (
); } if (overviewQuery.isLoading) { return (
); } if (overviewQuery.isError || !overviewQuery.data) { return (
); } const overview = overviewQuery.data; const certificates = certificatesQuery.data ?? []; const routes = routesQuery.data ?? []; const boundCertCount = new Set( overview.domains .map((domain) => domain.cert_id) .filter((id): id is number => id != null), ).size; return (

{overview.zone.domain}

{t('detailSubtitle')}

{t('tabOverview')} {t('tabDomains', { count: overview.domains.length })} {t('tabCertificates', { count: boundCertCount })} {tc('settings')} overviewQuery.refetch()} />

{t('basicInfo')}

{t('basicInfoDesc')}

{t('currentRoot')}

{overview.zone.domain}

{t('domainCount')}

{overview.domains.length}

{t('dangerZone')}

{t('dangerZoneDesc')}

{t('deleteZoneTitle')} {t('deleteZoneDesc', { domain: overview.zone.domain })} {tc('cancel')} { event.preventDefault(); deleteZone.mutate(); }} > {deleteZone.isPending ? t('deleting') : t('confirmDelete')}
); }