'use client'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useEffect, useState, type FormEvent } from 'react'; import { useForm } from 'react-hook-form'; import { InlineMessage } from '@/components/feedback/inline-message'; import { AppCard } from '@/components/ui/app-card'; import { AppModal } from '@/components/ui/app-modal'; import { createTlsCertificate, importTlsCertificateFiles, } from '@/features/tls-certificates/api/tls-certificates'; import type { TlsCertificateItem } from '@/features/tls-certificates/types'; import { defaultFileImportValues, defaultManualImportValues, type FileImportFormValues, manualImportSchema, type ManualImportFormValues, } from '@/features/websites/schemas'; import { getErrorMessage, toFilePayload, toManualPayload, } from '@/features/websites/utils'; import { PrimaryButton, ResourceField, ResourceInput, ResourceTextarea, SecondaryButton, } from '@/features/shared/components/resource-primitives'; type FeedbackState = { tone: 'success' | 'danger'; message: string; }; interface CertificateImportModalProps { isOpen: boolean; onClose: () => void; onImported?: (certificate: TlsCertificateItem) => void; } export function CertificateImportModal({ isOpen, onClose, onImported, }: CertificateImportModalProps) { const queryClient = useQueryClient(); const [importMode, setImportMode] = useState<'manual' | 'file'>('manual'); const [feedback, setFeedback] = useState(null); const [fileForm, setFileForm] = useState(defaultFileImportValues); const [certFile, setCertFile] = useState(null); const [keyFile, setKeyFile] = useState(null); const [fileInputNonce, setFileInputNonce] = useState(0); const manualForm = useForm({ resolver: zodResolver(manualImportSchema), defaultValues: defaultManualImportValues, }); useEffect(() => { if (!isOpen) { return; } setFeedback(null); }, [isOpen]); const resetFileForm = () => { setFileForm(defaultFileImportValues); setCertFile(null); setKeyFile(null); setFileInputNonce((value) => value + 1); }; const resetAll = () => { setImportMode('manual'); setFeedback(null); manualForm.reset(defaultManualImportValues); resetFileForm(); }; const invalidateCertificateQueries = async () => { await Promise.all([ queryClient.invalidateQueries({ queryKey: ['tls-certificates'] }), queryClient.invalidateQueries({ queryKey: ['managed-domains'] }), ]); }; const manualImportMutation = useMutation({ mutationFn: async (values: ManualImportFormValues) => createTlsCertificate(toManualPayload(values)), onSuccess: async (certificate) => { await invalidateCertificateQueries(); onImported?.(certificate); resetAll(); onClose(); }, onError: (error) => { setFeedback({ tone: 'danger', message: getErrorMessage(error) }); }, }); const fileImportMutation = useMutation({ mutationFn: async (values: FileImportFormValues) => importTlsCertificateFiles(toFilePayload(values, certFile, keyFile)), onSuccess: async (certificate) => { await invalidateCertificateQueries(); onImported?.(certificate); resetAll(); onClose(); }, onError: (error) => { setFeedback({ tone: 'danger', message: getErrorMessage(error) }); }, }); const handleManualSubmit = manualForm.handleSubmit((values) => { setFeedback(null); manualImportMutation.mutate(values); }); const handleFileSubmit = (event: FormEvent) => { event.preventDefault(); setFeedback(null); fileImportMutation.mutate(fileForm); }; const handleClose = () => { resetAll(); onClose(); }; return (
{feedback ? ( ) : null}
{importMode === 'manual' ? (
{manualImportMutation.isPending ? '导入中...' : '导入证书'}
) : (
setFileForm((current) => ({ ...current, name: event.target.value, })) } placeholder="wildcard-example" /> setFileForm((current) => ({ ...current, remark: event.target.value, })) } placeholder="例如:泛域名生产证书" />
setCertFile(event.target.files?.[0] ?? null) } /> setKeyFile(event.target.files?.[0] ?? null) } />
{fileImportMutation.isPending ? '上传中...' : '上传文件'} 清空文件
)}
); }