'use client'; import { useEffect, useMemo } from 'react'; import { zodResolver } from '@hookform/resolvers/zod'; import { useQuery } from '@tanstack/react-query'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Switch } from '@/components/ui/switch'; import type { ProxyRouteItem } from '@/lib/services/openflare'; import { zoneQueryKey, ZoneService } from '@/lib/services/openflare'; import { useTranslations } from 'next-intl'; import { listAllZoneDomains } from '../../components/helpers'; import { ZoneDomainSelector } from '../../components/zone-domain-selector'; import { proxyRouteFormIds } from '../helpers'; import { useRouteSectionSave } from '../hooks/use-route-section-save'; import { SectionShell } from './section-shell'; type DomainSettingsValues = { site_name: string; zone_domain_ids: number[]; enabled: boolean; redirect_http: boolean; }; interface DomainSectionProps { route: ProxyRouteItem; onRouteUpdate: (route: ProxyRouteItem) => void; onSavingChange?: (saving: boolean) => void; } export function DomainSection({ route, onRouteUpdate, onSavingChange, }: DomainSectionProps) { const t = useTranslations('proxyRoutes'); const domainSettingsSchema = z.object({ site_name: z .string() .trim() .min(1, t('validation.enterSiteId')) .max(255, t('validation.siteNameTooLong')), zone_domain_ids: z .array(z.number().int().positive()) .min(1, t('validation.selectAtLeastOneDomain')), enabled: z.boolean(), redirect_http: z.boolean(), }); const { saving, save } = useRouteSectionSave( route, onRouteUpdate, onSavingChange, ); const zonesQuery = useQuery({ queryKey: zoneQueryKey, queryFn: () => ZoneService.list(), }); const domainsQuery = useQuery({ queryKey: [...zoneQueryKey, 'all-domains'], queryFn: () => listAllZoneDomains(), }); const form = useForm({ resolver: zodResolver(domainSettingsSchema), defaultValues: { site_name: route.site_name, zone_domain_ids: route.zone_domain_ids ?? [], enabled: route.enabled, redirect_http: route.redirect_http, }, }); useEffect(() => { form.reset({ site_name: route.site_name, zone_domain_ids: route.zone_domain_ids ?? [], enabled: route.enabled, redirect_http: route.redirect_http, }); }, [form, route]); const selectedIDs = form.watch('zone_domain_ids'); const selectedDomains = useMemo(() => { const fromApi = domainsQuery.data ?? []; const byId = new Map(fromApi.map((domain) => [domain.id, domain])); // Prefer live catalog; fall back to route-bound domains for display before catalog loads. return selectedIDs .map((id) => { const catalog = byId.get(id); if (catalog) { return catalog; } const bound = (route.zone_domains ?? []).find((item) => item.id === id); if (!bound) { return null; } return { id: bound.id, zone_id: bound.zone_id, proxy_route_id: route.id, domain: bound.domain, cert_id: bound.cert_id, created_at: '', updated_at: '', }; }) .filter((item): item is NonNullable => item != null); }, [domainsQuery.data, route.id, route.zone_domains, selectedIDs]); const hasCertificate = selectedDomains.some( (domain) => domain.cert_id != null, ); return (
{ if (values.redirect_http && !hasCertificate) { form.setError('redirect_http', { message: t('redirectNeedsCert'), }); return; } await save( { site_name: values.site_name.trim(), zone_domain_ids: values.zone_domain_ids, enabled: values.enabled, enable_https: hasCertificate, redirect_http: hasCertificate ? values.redirect_http : false, }, t('domainSettingsSaved'), ); })} > (
{t('enableSite')} {t('enableSiteDesc')}
)} /> ( {t('siteId')} {t('siteIdStableDesc')} )} /> ( {t('bindDomains')} { await domainsQuery.refetch(); }} /> {t('bindDomainsHint')} )} /> (
{t('redirectHttp')} {hasCertificate ? t('redirectHttpEnabledHint') : t('redirectHttpNeedsCert')}
)} />
); }