import { useEffect, useState } from 'react'; import { msg } from '@lingui/core/macro'; import { Trans, useLingui } from '@lingui/react/macro'; import { motion } from 'framer-motion'; import { ArrowLeftIcon, CopyPlusIcon, DownloadCloudIcon, EyeIcon, LinkIcon, MousePointer, SendIcon, SettingsIcon, Trash2Icon, Upload, } from 'lucide-react'; import { useNavigate, useSearchParams } from 'react-router'; import { Link } from 'react-router'; import { match } from 'ts-pattern'; import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider'; import { mapSecondaryIdToDocumentId, mapSecondaryIdToTemplateId, } from '@documenso/lib/utils/envelope'; import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams'; import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out'; import { Button } from '@documenso/ui/primitives/button'; import { Separator } from '@documenso/ui/primitives/separator'; import { SpinnerBox } from '@documenso/ui/primitives/spinner'; import { DocumentDeleteDialog } from '~/components/dialogs/document-delete-dialog'; import { EnvelopeDistributeDialog } from '~/components/dialogs/envelope-distribute-dialog'; import { EnvelopeDuplicateDialog } from '~/components/dialogs/envelope-duplicate-dialog'; import { EnvelopeRedistributeDialog } from '~/components/dialogs/envelope-redistribute-dialog'; import { TemplateDeleteDialog } from '~/components/dialogs/template-delete-dialog'; import { TemplateDirectLinkDialog } from '~/components/dialogs/template-direct-link-dialog'; import { EnvelopeEditorSettingsDialog } from '~/components/general/envelope-editor/envelope-editor-settings-dialog'; import { useCurrentTeam } from '~/providers/team'; import EnvelopeEditorHeader from './envelope-editor-header'; import { EnvelopeEditorPageFields } from './envelope-editor-page-fields'; import { EnvelopeEditorPagePreview } from './envelope-editor-page-preview'; import { EnvelopeEditorPageUpload } from './envelope-editor-page-upload'; type EnvelopeEditorStep = 'upload' | 'addFields' | 'preview'; const envelopeEditorSteps = [ { id: 'upload', order: 1, title: msg`Document & Recipients`, icon: Upload, description: msg`Upload documents and add recipients`, }, { id: 'addFields', order: 2, title: msg`Add Fields`, icon: MousePointer, description: msg`Place and configure form fields in the document`, }, { id: 'preview', order: 3, title: msg`Preview`, icon: EyeIcon, description: msg`Preview the document before sending`, }, ]; export default function EnvelopeEditor() { const { t } = useLingui(); const navigate = useNavigate(); const team = useCurrentTeam(); const { envelope, isDocument, isTemplate, isAutosaving, flushAutosave } = useCurrentEnvelopeEditor(); const [searchParams, setSearchParams] = useSearchParams(); const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false); const [isStepLoading, setIsStepLoading] = useState(false); const [currentStep, setCurrentStep] = useState(() => { const searchParamStep = searchParams.get('step') as EnvelopeEditorStep | undefined; // Empty URL param equals upload, otherwise use the step URL param if (!searchParamStep) { return 'upload'; } const validSteps: EnvelopeEditorStep[] = ['upload', 'addFields', 'preview']; if (validSteps.includes(searchParamStep)) { return searchParamStep; } return 'upload'; }); const documentsPath = formatDocumentsPath(team.url); const templatesPath = formatTemplatesPath(team.url); const navigateToStep = (step: EnvelopeEditorStep) => { setCurrentStep(step); flushAutosave(); if (!isStepLoading && isAutosaving) { setIsStepLoading(true); } // Update URL params: empty for upload, otherwise set the step if (step === 'upload') { setSearchParams((prev) => { const newParams = new URLSearchParams(prev); newParams.delete('step'); return newParams; }); } else { setSearchParams((prev) => { const newParams = new URLSearchParams(prev); newParams.set('step', step); return newParams; }); } }; useEffect(() => { if (!isAutosaving) { setIsStepLoading(false); } }, [isAutosaving]); const currentStepData = envelopeEditorSteps.find((step) => step.id === currentStep) || envelopeEditorSteps[0]; return (
{/* Main Content Area */}
{/* Left Section - Step Navigation */}
{/* Left section step selector. */}

{isDocument ? Document Editor : Template Editor} Step {currentStepData.order}/{envelopeEditorSteps.length}

{envelopeEditorSteps.map((step) => { const Icon = step.icon; const isActive = currentStep === step.id; return (
navigateToStep(step.id as EnvelopeEditorStep)} >
{t(step.title)}
{t(step.description)}
); })}
{/* Quick Actions. */}

Quick Actions

{isDocument && ( Send Document } /> )} {isDocument && ( Resend Document } /> )} {isDocument ? Document Settings : Template Settings} } /> {/* Todo: Envelopes */} {/* */} {isTemplate && ( Direct Link } /> )} {isDocument ? ( Duplicate Document ) : ( Duplicate Template )} } /> {/* Todo: Allow selecting which document to download and/or the original */}
{isDocument ? ( { await navigate(documentsPath); }} /> ) : ( { await navigate(templatesPath); }} /> )} {/* Footer of left sidebar. */}
{/* Main Content - Changes based on current step */}

{isAutosaving ? 'Autosaving...' : 'Not autosaving'}

{match({ currentStep, isStepLoading }) .with({ isStepLoading: true }, () => ) .with({ currentStep: 'upload' }, () => ) .with({ currentStep: 'addFields' }, () => ) .with({ currentStep: 'preview' }, () => ) .exhaustive()}
); }