import { useMemo, useState } from 'react'; import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'; import type { DropResult } from '@hello-pangea/dnd'; import { msg } from '@lingui/core/macro'; import { Trans, useLingui } from '@lingui/react/macro'; import { DocumentStatus } from '@prisma/client'; import { FileWarningIcon, GripVerticalIcon, Loader2 } from 'lucide-react'; import { X } from 'lucide-react'; import { Link } from 'react-router'; import { useCurrentEnvelopeEditor, useDebounceFunction, } from '@documenso/lib/client-only/providers/envelope-editor-provider'; import { nanoid } from '@documenso/lib/universal/id'; import { putPdfFile } from '@documenso/lib/universal/upload/put-file'; import { canEnvelopeItemsBeModified } from '@documenso/lib/utils/envelope'; import { trpc } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@documenso/ui/primitives/card'; import { DocumentDropzone } from '@documenso/ui/primitives/document-dropzone'; import { EnvelopeItemDeleteDialog } from '~/components/dialogs/envelope-item-delete-dialog'; import { useCurrentTeam } from '~/providers/team'; import { EnvelopeEditorRecipientForm } from './envelope-editor-recipient-form'; import { EnvelopeItemTitleInput } from './envelope-editor-title-input'; type LocalFile = { id: string; title: string; envelopeItemId: string | null; isUploading: boolean; isError: boolean; }; export const EnvelopeEditorPageUpload = () => { const team = useCurrentTeam(); const { t } = useLingui(); const { envelope, setLocalEnvelope } = useCurrentEnvelopeEditor(); const [localFiles, setLocalFiles] = useState( envelope.envelopeItems .sort((a, b) => a.order - b.order) .map((item) => ({ id: item.id, title: item.title, envelopeItemId: item.id, isUploading: false, isError: false, })), ); const { mutateAsync: createEnvelopeItems, isPending: isCreatingEnvelopeItems } = trpc.envelope.item.createMany.useMutation({ onSuccess: (data) => { const createdEnvelopes = data.createdEnvelopeItems.filter( (item) => !envelope.envelopeItems.find((envelopeItem) => envelopeItem.id === item.id), ); setLocalEnvelope({ envelopeItems: [...envelope.envelopeItems, ...createdEnvelopes], }); }, }); const { mutateAsync: updateEnvelopeItems } = trpc.envelope.item.updateMany.useMutation({ onSuccess: (data) => { setLocalEnvelope({ envelopeItems: envelope.envelopeItems.map((originalItem) => { const updatedItem = data.updatedEnvelopeItems.find((item) => item.id === originalItem.id); if (updatedItem) { return { ...originalItem, ...updatedItem, }; } return originalItem; }), }); }, }); const canItemsBeModified = useMemo( () => canEnvelopeItemsBeModified(envelope, envelope.recipients), [envelope, envelope.recipients], ); const onFileDrop = async (files: File[]) => { const newUploadingFiles: (LocalFile & { file: File })[] = files.map((file) => ({ id: nanoid(), envelopeItemId: null, title: file.name, file, isUploading: true, isError: false, })); setLocalFiles((prev) => [...prev, ...newUploadingFiles]); const result = await Promise.all( files.map(async (file, index) => { try { const response = await putPdfFile(file); // Mark as uploaded (remove from uploading state) return { title: file.name, documentDataId: response.id, }; } catch (_error) { setLocalFiles((prev) => prev.map((uploadingFile) => uploadingFile.id === newUploadingFiles[index].id ? { ...uploadingFile, isError: true, isUploading: false } : uploadingFile, ), ); } }), ); const envelopeItemsToCreate = result.filter( (item): item is { title: string; documentDataId: string } => item !== undefined, ); const { createdEnvelopeItems } = await createEnvelopeItems({ envelopeId: envelope.id, items: envelopeItemsToCreate, }).catch((error) => { console.error(error); // Set error state on files in batch upload. setLocalFiles((prev) => prev.map((uploadingFile) => uploadingFile.id === newUploadingFiles.find((file) => file.id === uploadingFile.id)?.id ? { ...uploadingFile, isError: true, isUploading: false } : uploadingFile, ), ); throw error; }); setLocalFiles((prev) => { const filteredFiles = prev.filter( (uploadingFile) => uploadingFile.id !== newUploadingFiles.find((file) => file.id === uploadingFile.id)?.id, ); return filteredFiles.concat( createdEnvelopeItems.map((item) => ({ id: item.id, envelopeItemId: item.id, title: item.title, isUploading: false, isError: false, })), ); }); }; /** * Hide the envelope item from the list on deletion. */ const onFileDelete = (envelopeItemId: string) => { setLocalFiles((prev) => prev.filter((uploadingFile) => uploadingFile.id !== envelopeItemId)); setLocalEnvelope({ envelopeItems: envelope.envelopeItems.filter((item) => item.id !== envelopeItemId), }); }; /** * Handle drag end for reordering files. */ const onDragEnd = (result: DropResult) => { if (!result.destination) { return; } const items = Array.from(localFiles); const [reorderedItem] = items.splice(result.source.index, 1); items.splice(result.destination.index, 0, reorderedItem); setLocalFiles(items); debouncedUpdateEnvelopeItems(items); }; // Todo: Envelopes - Sync into envelopes data const debouncedUpdateEnvelopeItems = useDebounceFunction((files: LocalFile[]) => { void updateEnvelopeItems({ envelopeId: envelope.id, data: files .filter((item) => item.envelopeItemId) .map((item, index) => ({ envelopeItemId: item.envelopeItemId || '', order: index + 1, title: item.title, })), }); }, 1000); const onEnvelopeItemTitleChange = (envelopeItemId: string, title: string) => { const newLocalFilesValue = localFiles.map((uploadingFile) => uploadingFile.envelopeItemId === envelopeItemId ? { ...uploadingFile, title } : uploadingFile, ); setLocalFiles(newLocalFilesValue); debouncedUpdateEnvelopeItems(newLocalFilesValue); }; return (
Documents Add and configure multiple documents {/* Uploaded Files List */}
{(provided) => (
{localFiles.map((localFile, index) => ( {(provided, snapshot) => (
{localFile.envelopeItemId !== null ? ( { onEnvelopeItemTitleChange(localFile.envelopeItemId!, title); }} /> ) : (

{localFile.title}

)}
{localFile.isUploading ? ( Uploading ) : localFile.isError ? ( Something went wrong while uploading this file ) : //
2.4 MB • 3 pages
null}
{localFile.isUploading && (
)} {localFile.isError && (
)} {!localFile.isUploading && localFile.envelopeItemId && ( } /> )}
)}
))} {provided.placeholder}
)}
{/* Recipients Section */}
); };