'use client'; import {useEffect, useMemo} from 'react'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import {zodResolver} from '@hookform/resolvers/zod'; import {useForm} from 'react-hook-form'; import {Loader2} from 'lucide-react'; import {toast} from 'sonner'; import {z} from 'zod'; import {Button} from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import {Input} from '@/components/ui/input'; import {Label} from '@/components/ui/label'; import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select'; import { TlsCertificateService, ZoneDomainService, ZoneService, zoneQueryKey, type ZoneDomainItem, type ZoneItem, } from '@/lib/services/openflare'; import {previewZoneDomainInput, resolveZoneDomainInput} from './resolve-zone-domain-input'; const schema = z.object({ zone_id: z.string().min(1, '请选择 Zone'), domain_input: z.string().trim().min(1, '请输入域名'), cert_id: z.string(), }); type Values = z.infer; export function QuickCreateZoneDomainDialog({ open, onOpenChange, fixedZoneId, fixedZoneRoot, zones: zonesProp, onCreated, }: { open: boolean; onOpenChange(open: boolean): void; /** When set, Zone 选择器隐藏 */ fixedZoneId?: number; fixedZoneRoot?: string; zones?: ZoneItem[]; onCreated(domain: ZoneDomainItem): void | Promise; }) { const queryClient = useQueryClient(); const zonesQuery = useQuery({ queryKey: zoneQueryKey, queryFn: () => ZoneService.list(), enabled: open && !fixedZoneId && !zonesProp, }); const certificatesQuery = useQuery({ queryKey: ['openflare', 'tls-certificates'], queryFn: () => TlsCertificateService.list(), enabled: open, }); const zones = useMemo( () => zonesProp ?? zonesQuery.data ?? [], [zonesProp, zonesQuery.data], ); const fixedZone = useMemo(() => { if (!fixedZoneId) { return undefined; } return ( zones.find((zone) => zone.id === fixedZoneId) ?? (fixedZoneRoot ? ({id: fixedZoneId, domain: fixedZoneRoot, created_at: '', updated_at: ''} as ZoneItem) : undefined) ); }, [fixedZoneId, fixedZoneRoot, zones]); const form = useForm({ resolver: zodResolver(schema), defaultValues: { zone_id: fixedZoneId ? String(fixedZoneId) : '', domain_input: '', cert_id: '', }, }); useEffect(() => { if (!open) { return; } form.reset({ zone_id: fixedZoneId ? String(fixedZoneId) : '', domain_input: '', cert_id: '', }); }, [fixedZoneId, form, open]); const watchedZoneId = form.watch('zone_id'); const watchedInput = form.watch('domain_input'); const selectedZone = useMemo(() => { if (fixedZone) { return fixedZone; } const id = Number(watchedZoneId); return zones.find((zone) => zone.id === id); }, [fixedZone, watchedZoneId, zones]); const preview = selectedZone ? previewZoneDomainInput(watchedInput, selectedZone.domain) : ''; const mutation = useMutation({ mutationFn: async (values: Values) => { const zoneId = Number(values.zone_id); const zone = fixedZone ?? zones.find((item) => item.id === zoneId); if (!zone) { throw new Error('请选择 Zone'); } const resolved = resolveZoneDomainInput(values.domain_input, zone.domain); if (resolved.error || !resolved.domain) { throw new Error(resolved.error || '域名格式不合法'); } return ZoneDomainService.create(zone.id, { domain: resolved.domain, cert_id: values.cert_id ? Number(values.cert_id) : null, }); }, onSuccess: async (domain) => { toast.success('域名已添加', {description: domain.domain}); await Promise.all([ onCreated(domain), queryClient.invalidateQueries({queryKey: zoneQueryKey}), queryClient.invalidateQueries({queryKey: [...zoneQueryKey, 'all-domains']}), ]); onOpenChange(false); }, onError: (error) => toast.error(error instanceof Error ? error.message : '添加失败'), }); return ( 快捷新增域名 选择 Zone 后输入:子域名称(如 api)、@(根域)或完整 FQDN。
{ if (!selectedZone) { form.setError('zone_id', {message: '请选择 Zone'}); return; } const resolved = resolveZoneDomainInput( values.domain_input, selectedZone.domain, ); if (resolved.error) { form.setError('domain_input', {message: resolved.error}); return; } mutation.mutate(values); })} > {!fixedZoneId ? (
{form.formState.errors.zone_id ? (

{form.formState.errors.zone_id.message}

) : null}
) : (
Zone: {fixedZoneRoot || fixedZone?.domain || `#${fixedZoneId}`}
)}
{preview ? (

将创建: {preview}

) : (

示例:输入 api →{' '} api.zone.com; @ → 根域本身

)} {form.formState.errors.domain_input ? (

{form.formState.errors.domain_input.message}

) : null}
); }