'use client'; import { useRef, useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { UploadCloud } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Field, FieldDescription, FieldGroup, FieldLabel, } from '@/components/ui/field'; import { Progress } from '@/components/ui/progress'; import { Spinner } from '@/components/ui/spinner'; import { PagesService } from '@/lib/services/openflare'; import { cn } from '@/lib/utils'; import { deploymentsQueryKey, formatBytes, projectQueryKey, projectsQueryKey, } from './pages-utils'; const PAGES_PACKAGE_ACCEPT = '.zip,.tar.gz,.tgz,.tar.xz,.txz,.tar.bz2,.tbz2,.tbz,.tar,.7z'; const PAGES_PACKAGE_EXTENSIONS = [ '.zip', '.tar.gz', '.tgz', '.tar.xz', '.txz', '.tar.bz2', '.tbz2', '.tbz', '.tar', '.7z', ] as const; function isSupportedPagesPackage(fileName: string) { const lower = fileName.toLowerCase(); return PAGES_PACKAGE_EXTENSIONS.some((extension) => lower.endsWith(extension), ); } export function pagesEntryPath(rootDir: string, entryFile: string) { const root = rootDir.trim().replace(/^\/+|\/+$/g, ''); const entry = entryFile.trim().replace(/^\/+/, ''); return root ? `${root}/${entry}` : entry; } interface DeploymentUploadDialogProps { open: boolean; onOpenChange: (open: boolean) => void; projectId: number; rootDir: string; entryFile: string; } export function DeploymentUploadDialog({ open, onOpenChange, projectId, rootDir, entryFile, }: DeploymentUploadDialogProps) { const t = useTranslations('pages.upload'); const tCommon = useTranslations('common'); const queryClient = useQueryClient(); const fileInputRef = useRef(null); const [file, setFile] = useState(null); const [isDragActive, setIsDragActive] = useState(false); const [uploadProgress, setUploadProgress] = useState(null); const resetForm = () => { setFile(null); setIsDragActive(false); setUploadProgress(null); if (fileInputRef.current) fileInputRef.current.value = ''; }; const handleOpenChange = (nextOpen: boolean) => { if (!nextOpen) resetForm(); onOpenChange(nextOpen); }; const uploadMutation = useMutation({ mutationFn: () => { if (!file) throw new Error(t('selectFile')); return PagesService.uploadDeployment(projectId, { file, onProgress: setUploadProgress, }); }, onSuccess: async () => { toast.success(t('success')); await Promise.all([ queryClient.invalidateQueries({ queryKey: deploymentsQueryKey(projectId), }), queryClient.invalidateQueries({ queryKey: projectQueryKey(projectId) }), queryClient.invalidateQueries({ queryKey: projectsQueryKey }), ]); handleOpenChange(false); }, onError: (error) => { toast.error(error instanceof Error ? error.message : t('failed')); setUploadProgress(null); }, }); const handleFileSelect = (selected: File | null) => { if (!selected) return; if (!isSupportedPagesPackage(selected.name)) { toast.error(t('unsupported')); return; } setFile(selected); }; return ( {t('title')} {t('description')} {t('localPackage')} handleFileSelect(event.target.files?.[0] ?? null) } /> {file ? ( {t('selected', { name: file.name, size: formatBytes(file.size), })} ) : ( {t('chooseArchive')} )} {t('entry')}
{pagesEntryPath(rootDir, entryFile)}
{t('entryHint')}
{uploadProgress !== null ? (
{uploadProgress >= 100 ? t('processing') : t('progress')} {uploadProgress >= 100 ? t('pleaseWait') : `${uploadProgress}%`}
) : null}
); }