'use client'; import { useMemo, useState } from 'react'; import Link from 'next/link'; import { ExternalLink, Plus } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Input } from '@/components/ui/input'; import { cn } from '@/lib/utils'; import type { ZoneDomainItem, ZoneItem } from '@/lib/services/openflare'; import { useTranslations } from 'next-intl'; import { QuickCreateZoneDomainDialog } from '../../websites/components/quick-create-zone-domain-dialog'; export interface ZoneDomainSelectorProps { value: number[]; onChange: (ids: number[]) => void; domains: ZoneDomainItem[]; zones?: ZoneItem[]; /** Current route ID: domains bound to this route remain selectable. */ currentRouteId?: number | null; disabled?: boolean; className?: string; /** Called after a domain is created so parent can refresh the catalog. */ onDomainCreated?: (domain: ZoneDomainItem) => void | Promise; } export function ZoneDomainSelector({ value, onChange, domains, zones = [], currentRouteId = null, disabled = false, className, onDomainCreated, }: ZoneDomainSelectorProps) { const t = useTranslations('proxyRoutes'); const [keyword, setKeyword] = useState(''); const [createOpen, setCreateOpen] = useState(false); const selected = useMemo(() => new Set(value), [value]); const zoneMap = useMemo( () => new Map(zones.map((zone) => [zone.id, zone.domain])), [zones], ); /** Hide domains already bound to another route; keep unbound + current route. */ const availableDomains = useMemo( () => domains.filter( (domain) => domain.proxy_route_id == null || domain.proxy_route_id === currentRouteId, ), [currentRouteId, domains], ); const filtered = useMemo(() => { const normalized = keyword.trim().toLowerCase(); const list = [...availableDomains].sort((a, b) => a.domain.localeCompare(b.domain), ); if (!normalized) { return list; } return list.filter((domain) => { const zoneRoot = zoneMap.get(domain.zone_id) ?? ''; return ( domain.domain.toLowerCase().includes(normalized) || zoneRoot.toLowerCase().includes(normalized) || String(domain.id).includes(normalized) ); }); }, [availableDomains, keyword, zoneMap]); const toggle = (domain: ZoneDomainItem) => { if (disabled) { return; } if ( domain.proxy_route_id != null && domain.proxy_route_id !== currentRouteId ) { return; } if (selected.has(domain.id)) { onChange(value.filter((id) => id !== domain.id)); return; } onChange([...value, domain.id].sort((a, b) => a - b)); }; return (
setKeyword(event.target.value)} placeholder={t('searchDomainOrZone')} disabled={disabled} className='sm:flex-1' />
{filtered.length === 0 ? (
{domains.length === 0 ? ( t.rich('noZoneDomains', { websites: (chunks) => ( {chunks} ), }) ) : availableDomains.length === 0 ? ( t('noBindableDomains') ) : ( t('noMatchingDomains') )}
) : (
{filtered.map((domain) => { const checked = selected.has(domain.id); const zoneRoot = zoneMap.get(domain.zone_id); return ( ); })}
)} { // Auto-select the new domain for this route binding. if (!value.includes(domain.id)) { onChange([...value, domain.id].sort((a, b) => a - b)); } await onDomainCreated?.(domain); }} />
); }